Secure Microservices with Auth0 Authentication & Authorization
In this article, we will explore how SpringBoot microservices can be secured by enabling authorization through Auth0.
The purpose of writing this article is to provide a detailed step-by-step guide that allows developers to:
What is Auth0?
Auth0 is a popular authentication and authorization platform. It provides a rich set of tools and features to incorporate different authentication and authorization flows into your applications. Auth0 can be used for traditional web applications, as well as single page web apps and mobile applications. Here is the link to the Auth0 platform: https://auth0.com/
Why SpringBoot?
SpringBoot is a proven Java-based open-source framework that provides the building blocks for creating microservices and web applications. It decreases the need for developers to write boilerplate code, annotations, and XML configurations. Spring Boot provides an opinionated approach to building Spring applications and integrates the suite of Spring Boot projects so that application teams can build production-ready microservices.
OAuth 2.0 authorization using Auth0
OAuth 2.0 authorization framework is a protocol that allows a user to grant a third-party application access to the user's protected resources, without revealing their credentials or identity information. For example, if you have granted any application like DocuSign access to store PDFs into your Google Drive, you have used the OAuth 2.0 authorization flow.
Auth0 uses the OIDC and OAuth 2.0 authorization framework to authenticate users and get their authorization to access protected resources.
Auth0 uses the JWT (JSON Web Token) token format to exchange access tokens for authorization flow. When an application or user authenticates with Auth0, it requests the scopes or permissions that it wants access to. If the user is authorized for the requested scope/permission, the JWT token returned by Auth0 will contain the scope/permission within the scope list.
Security Concerns with Traditional Authorization Code Flow
The Authorization Code flow as defined in (https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow) involves the exchange of authorization code for an access token. However, Single Page Apps that run on the browser cannot securely store client secrets. Also, the use of custom URL schemes to capture redirects can cause a malicious application to get access to the Authorization code.
To mitigate these security issues, the OAuth 2.0 framework provides an enhanced version of the Authorization code flow that makes use of a Proof Key for Code Exchange (PKCE).
Authorization Code Flow with PKCE
The Authorization code flow with PKCE is now a recommended standard for not just SPAs but for all traditional web applications, backend APIs, and native applications.
Below is the sequence of steps within this authorization flow:
The above authorization flow is very similar to the traditional Authorization Code Flow, apart from the below additional steps:
Configure Auth0 Tenant to support PKCE authorization code flow
The following sections provide a walk-through of how to set up your API within the Auth0 dashboard, to enable PKCE authorization code flow.
Setup Auth0 account
You can create a free Auth0 account using https://auth0.com/signup
Create API
Create API within the Auth0 dashboard using the below details:
Once the API is created, note down the key identifiers below which will be required during the Spring Boot application setup
Configure Permissions
Click on the API (auth0-test-api) that you just created and navigate to the permissions sub-tab.
Add the below permissions:
RBAC
Within the Setting sub-tab, enable RBAC to enforce evaluation of role and permission assignment during login.
Callback URL setup
Navigate to Applications -> auth0-test-api -> Settings
Add the below callback URLs:
Note:
Configure Grant type for your application
Navigate to Applications -> auth0-test-api -> Settings
Scroll down to Advanced settings and enable "Authorization Code" grant_type.
Configure Spring Boot App to enable PKCE authorization code flow
The following sections provide a walk-through of how to implement authorization code flow with PKCE in your spring boot application.
Add auth0 properties to resources/application.yml
okta:
oauth2:
groupsClaim: permissions
issuer: https://<tenantName>.us.auth0.com/
client-id: <insert clienId from Applications->autho-test-api-> Settings tab>
client-secret: <insert client-secret from Applications->autho-test-api-> Settings tab>
audience: https://auth0-test-api/
post-logout-redirect-uri: "{baseUrl}"
authorization-grant-type: authorization_code
Update pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
</dependencies>
Create a custom SecurityConfig class with the below annotations
@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
Create SecurityFilterChain Bean
领英推荐
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository, "/oauth2/authorization");
authorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
http.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/", "/images/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/internal/**").denyAll()
.requestMatchers("/api/internal-scoped/read/**").hasAuthority("SCOPE_read:securities")
.requestMatchers("/api/internal-scoped/write/**").hasAuthority("SCOPE_write:securities")
.anyRequest().denyAll())
.cors(customizer ->
customizer.configurationSource(corsConfigurationSource()))
.oauth2Login(oauth2-> oauth2.authorizationEndpoint(
authorization -> authorization
.authorizationRequestResolver(authorizationRequestResolver))
.failureHandler(restAuthenticationFailureHandler))
.oauth2ResourceServer(oauth2ResourceServer
-> oauth2ResourceServer.jwt(withDefaults())
.authenticationEntryPoint(customAuthenticationEntryPoint))
.exceptionHandling(Customizer.withDefaults())
.logout(logout -> logout.addLogoutHandler(logoutHandler()));
return http.build();
}
When @EnableWebSecurity is added to the SecurityConfig class, Spring boot disables access to all API URLs by default. We need to configure the custom SecurityFilterChain bean to allow access to URLs in a controlled manner.
http.authorizeHttpRequests method is used to declare custom authorization rules.
Authorizing endpoint
As shown above, authorize.requestMatchers can be used to apply different authorization rules to different URL patterns.
The rules are evaluated in the order in which Matchers are listed, applying only the first rule that matches a given URL. If a given URL does not match any pattern, you can configure an explicit denyAll() as the last rule, as shown in the above code example.
Once a request URL is matched, we can authorize by using the below rules:
Enable PKCE within http.oauth2login()
Customize the default oauth2login() behavior by adding a custom authorizationRequestResolver that enables PKCE support.
DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(this.clientRegistrationRepository, "/oauth2/authorization");
authorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
Enable OAuth2 Resource Server & JWT
Since our sample application here will be exposing APIs, it will act as the Resource Server in the Authorization Flow.
Configure SecurityFilterChain with oauth2ResourceServer support and enable "jwt-encoded" bearer token support as shown below.
.oauth2ResourceServer(oauth2ResourceServer
-> oauth2ResourceServer.jwt(withDefaults())
Update Controller class
Once the token is authenticated and authorized based on the scope/permissions within the access token, the request reaches the appropriate controller method.
Here, the @AuthenticationPrincipal object can be used if we need to do additional validation for the claims within the jwt token.
@GetMapping("/api/internal-scoped/read/")
@PreAuthorize("hasAuthority('SCOPE_read:securities')")
public ResponseEntity<Object> testApiInternalReadScoped(Model model
, @AuthenticationPrincipal Jwt principal) {
if (principal != null) {
System.out.println("Entered testApiInternalReadScoped");
Authentication authDetails = SecurityContextHolder.getContext().getAuthentication();
return new ResponseEntity<>(authDetails.getPrincipal(), HttpStatus.OK);
}
return null;
}
Test the secured API locally using Postman
Auth0 Authentication API Debugger
To test your API, you can leverage Auth0's API Debugger extension. This extension will help you to create a "code verifier" and test the OAuth 2.0 authorization flow with PKCE.
On the Auth0 dashboard,
Setup Configuration for Debugger
Generate Authorization Code
Click on the "OAuth2 / OIDC" button and scroll down to the "Settings" section.
Choose below details:
Now, scroll to the top, and click on the"OAuth2 / OIDC Login".
You should now see the authorization code as shown in the below screenshot.
If you click on the "Login" tab above and select "OAuth2 / OIDC", you can see that the Auth0 debugger generated a "code verifier" on your behalf, and used that to request Auth0 Authorization server for an "authorization code".
Generate Access Token
Now, we can request an access token using the Authorization code generated in the previous step.
In the OAuth2 / OIDC tab, scroll down to the "Settings" section:
Use Access Token as Bearer Token within your Postman App
Under the Authorization section, choose Type="Bearer Token", and enter the token as shown below.
If you have configured everything correctly based on the steps outlined above, you should get a response code of 200 OK. This means the bearer token was successfully validated by our custom SecurityConfig class, and the token was authorized for the required scope "read:securities".
In my example, I am returning the jwt token in the response, so that you can go through the different sections of the token in detail. If you need to do any additional validation rules, you can apply those in your Controller method.
Code Sample
You can find the entire working project used in this article on GitHub.
The api-chassis-security is bundled as a library package that can be imported and used by your microservice.
Below is a sample microservice project that uses the above library to enable authorization:
Conclusion
I hope this article helped you get a better understanding of how authorization flow works in practice and how you can implement the same in your microservices using Auth0.
References:
Treasury, ALM and Risk Management
9 个月Useful tips. Il keep this in mind.
AI & Data Director - Platform Engineering @ EY
9 个月Love it, keep writing good stuff Ashish!