Secure Microservices with Auth0 Authentication & Authorization

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:

  • Implement authentication and authorization flow into Spring boot microservices using Auth0 SDK
  • Enable Proof Key for Code Exchange (PKCE) in a spring boot confidential client
  • Package the auth0 authorization flow as a chassis library that can be imported and re-used by other microservices
  • Test the secured Spring Boot microservice locally using Auth0 API Debugger Extension and Postman

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:

Authorization Code Flow with PKCE using Auth0


The above authorization flow is very similar to the traditional Authorization Code Flow, apart from the below additional steps:

  • Auth0 SDK generates the code verifier and code challenge.
  • Auth0 SDK calls the /authorize endpoint along with the code challenge. The Auth0 authorization server stores this code challenge, and sends the authorization code in the response.
  • Auth0 SDK calls the /token endpoint along with the authorization code and code verifier.
  • The Auth0 authorization server verifies the code verifier with the code challenge and sends the access token as a response.
  • This access token can then be used as a bearer token while calling the secured API resource.


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:

  • read:securities
  • write:securities

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:

  • Replace tenant-id with your tenant-id from Auth0 dashboard.
  • Since our Spring Boot application is running locally on port 3000, we have added the https://localhost:3000 URL.

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:

  • permitAll() - permit matched request. This is used for public endpoints that do not need authorization.
  • denyAll() - deny the matched request.
  • hasAuthority() - Allow request if it has the required scope or permissions (eg: read:securities)

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,

  • click on "Extensions" on your left pane
  • Search and install "Auth0 Authentication API Debugger".
  • Click on the installed extension. You should see something similar to the below screenshot.

Setup Configuration for Debugger


Generate Authorization Code

Click on the "OAuth2 / OIDC" button and scroll down to the "Settings" section.

Choose below details:

  • Click on the toggle button to enable PKCE
  • Specify Audience -> "https://auth0-test-api/"
  • Enter Authorization code -> code
  • Enter Response Type -> code
  • Enter scope -> read:securities

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:

  • Specify Response Type -> token
  • Now, scroll back to the top, and click on "OAuth2 / OIDC Login".
  • In a few milliseconds, you should see an Access Token generated similar to below.


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:

Rohan Pai, FRM

Treasury, ALM and Risk Management

9 个月

Useful tips. Il keep this in mind.

Tze Shin Choo

AI & Data Director - Platform Engineering @ EY

9 个月

Love it, keep writing good stuff Ashish!

要查看或添加评论,请登录

社区洞察

其他会员也浏览了