the/experts. Blog

Patrick Dronk
Patrick Dronk

Posted on

Spring Boot, OAuth2 and Springdoc.

If you're building an API that is intended for external clients, it's essential to have good documentation and authentication mechanisms in place. In this blog, we'll take a closer look at how we built a Spring Boot API and integrated it with AWS Cognito for authentication.

What is AWS Cognito?

AWS Cognito is a managed service from Amazon that provides authentication, authorization, and user management for web and mobile applications. It allows you to create and maintain a user directory and integrate it with your applications seamlessly. With AWS Cognito, you can handle user sign up, sign-in, and access control in a secure and scalable way.

What is AWS CDK?

AWS Cloud Development Kit (CDK) is an open-source software development framework that allows you to define your cloud infrastructure in code. With AWS CDK, you can define your cloud resources using familiar programming languages like TypeScript, Java, and Python, and then deploy them with ease. AWS CDK makes it easy to automate your infrastructure deployment and ensures that your resources are consistent and repeatable.

We deployed the Cognito stack via CDK. The following infrastructure was written in TypeScript and deployed via CDK: typescript

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const userPool = new UserPool(this, 'UserPool', {
      userPoolName: 'user-pool',
    });

    userPool.addDomain('user-pool-domain', {
      cognitoDomain: {
        domainPrefix: 'user-pool-domain
      },
    });

    userPool.addResourceServer('resource-server', {
      identifier: 'resource-server',
      scopes: [
        {
          scopeName: 'resource-server/api',
          scopeDescription: 'api access',
        },
      ],
    });

    userPool.addClient('api-client', {
      userPoolClientName: 'api-client',
      generateSecret: true,
      oAuth: {
        flows: {
          clientCredentials: true,
        },
        scopes: [
          OAuthScope.custom('resource-server/api'),
        ],
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

After deploying the Cognito stack, we started a new Spring Boot service with the following dependencies:

implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
Enter fullscreen mode Exit fullscreen mode

Next, we created a controller to have an endpoint to test out the setup:

@RestController
@RequestMapping("/order")
class ShippingOrderController {
    @PostMapping
    fun createOrder(@RequestBody command: CreateOrderCommand) {
        println("Creating order for $command")
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we started configuring Spring Boot security. We used the @EnableWebSecurity and @EnableMethodSecurity(prePostEnabled = true) annotations to enable Spring Security and method-level security, respectively.

In the SecurityConfiguration class, we defined an OAuth 2.0 security scheme and set up the security filter chain for our API. We also added the necessary configurations for the OpenAPI documentation.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@SecurityScheme(
    name = "Authorization",
    type = SecuritySchemeType.OAUTH2,
    `in` = SecuritySchemeIn.HEADER,
    flows = OAuthFlows(
        clientCredentials = OAuthFlow(
            authorizationUrl = "https://some.auth.eu-west-1.amazoncognito.com/oauth2/authorize",
            tokenUrl = "https://some.auth.eu-west-1.amazoncognito.com/oauth2/token",
            scopes = []
        )
    )
)
class SecurityConfiguration {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain? {
        return http
            .csrf().disable() // we are not using any form based submits.
            .authorizeHttpRequests()
            .requestMatchers("/actuator/**").permitAll() // we want to access the actuator endpoints 
            .requestMatchers("/v3/api-docs/**").permitAll() // we want to access the swagger docs
            .requestMatchers("/swagger-ui/**").permitAll() // we want to access the swagger docs
            .anyRequest().authenticated() // any other request should be authenticated
            .and()
            .oauth2ResourceServer().jwt() // we are using jwt tokens
            .and().and()
            .build()
    }

    @Bean
    fun nexusOpenApi(): OpenAPI? {
        return OpenAPI()
            .info(
                Info()
                    .title("ATMS API")
                    .description("ATMS API")
                    .version("1.0.0")
            )
            .security(listOf(SecurityRequirement().addList("Authorization"))) // we want to use the Authorization security scheme
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we set the issuer URL in the application.properties file:

spring.security.oauth2.resourceserver.jwt.issuer-uri=https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_ourId
Enter fullscreen mode Exit fullscreen mode

Now we can access our endpoint and do a login with the clientId and clientSecret, and it will automatically generate a token for us and set the correct headers.

Final result

In summary, integrating AWS Cognito with your Spring Boot API provides a secure and scalable way to handle authentication and authorization for your external clients. With the help of the springdoc module and AWS CDK, you can easily create good documentation and automate your infrastructure deployment.

Discussion (0)