Example of Spring Boot Application Authentication with AWS Cognito

In this post, I plan to show an example of Spring Boot Application authentication with AWS Cognito. I will show two flows –

  1. OIDC Authentication
  2. SAML Authentication

AWS Cognito

AWS Cognito is a web service from AWS. Cognito is a user directory as well as an authentication mechanism service. In the enterprise industry, every application has two requirements from a user perspective.

  1. User Directory and Synchronization
  2. User Authentication

Cognito makes this easier by allowing the creation of a user pool or an identity pool. Once you have a pool, you can configure an application with the various settings for authentication. Another major advantage of Cognito is that it offers industry-standard security authentication protocols like OAuth 2.0, OpenID Connect, SAML.

There are equally other features like user access handling, but we won’t go into detail about that as part of this post. In this post, I will further show how we can create a user pool and configure an application to use that user pool.

Spring Boot Application

We will use a simple Spring Boot Application. We will integrate this application with AWS Cognito for authentication.

Spring Boot Setup

Add the following dependencies in the Gradle file


 implementation 'org.springframework.boot:spring-boot-starter-security'
 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
 implementation 'org.springframework.boot:spring-boot-starter-web'
 implementation 'org.springframework.security:spring-security-oauth2-client'
 implementation 'org.springframework.security:spring-security-oauth2-jose'

And the application.properties will need oauth2 configuration properties:


# ====================================================================================
## SSL Keystore for HTTPS
# ====================================================================================
#security.require-ssl=true
server.port=8743
server.ssl.key-store-password=******
server.ssl.key-store=classpath:sssstore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=selfsigned_localhost
# =====================================================================================
spring.security.oauth2.client.registration.cognito.clientId=7mtivo8jobdtd6dvocldkmu6qk
spring.security.oauth2.client.registration.cognito.clientSecret=*********
spring.security.oauth2.client.registration.cognito.scope=openid
spring.security.oauth2.client.registration.cognito.redirect-uri=https://localhost:8743/login/oauth2/code/cognito
spring.security.oauth2.client.registration.cognito.clientName=SpringBootCognitoDemo
spring.security.oauth2.client.registration.cognito.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.cognito.issueUri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}
spring.security.oauth2.client.provider.cognito.authorization-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/authorize
spring.security.oauth2.client.provider.cognito.token-uri=https://{customDomain}.auth.{awszone}.amazoncognito.com/oauth2/token
spring.security.oauth2.client.provider.cognito.jwk-set-uri=https://cognito-idp.{awszone}.amazonaws.com/{userPoolId}/.well-known/jwks.json
spring.security.oauth2.client.provider.cognito.user-name-attribute= cognito:username

Security Configuration

Our Main Controller class will look like below:



@Controller
public class MainController
{
    @GetMapping("/")
    public String home(Model model, Principal principal)
    {
        model.addAttribute("username", principal.getName());
        return "index";
    }
}

So when we will access our application, it will redirect to AWS Cognito UI for authentication. Based on the selected flow OIDC or SAML, the authentication will happen.

SecurityConfig will be as below:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    public CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.csrf()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .oauth2Login()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler)
                ;

    }
}

Our simple index template will look like below:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta content="width=device-width, initial-scale=1" name="viewport">
    <title>Cognito Demo</title>
</head>
<body>
<div class="container">
    <h1 class="title">OAuth 2.0 Spring Security Cognito Demo</h1>
    <div>
        <div class="box">
            Hello, <strong th:text="${username}"></strong>!
        </div>
        <a th:href="@{/logout}" class="btn btn-primary">Logout</a>
    </div>
</div>
</body>
</html>

 

Create a User Pool

Once you log in to AWS Console, select Cognito as AWS Service. The first screen will show you two options – Create a user pool and Create an identity pool.

We will select Create a user pool.

Authentication with AWS Cognito - Create User Pool

Enter a suitable name for your user pool and select Step through settings. This will allow us to modify any settings that we don’t want to use for our use case.

Now select user pool attributes for users. These attributes allow us to identify the user and what a user can enter during authentication.

Authentication with AWS Cognito - User Attributes

For our demo, we have selected Email address, family name, and given name as attributes. So the user will log in to the application using an email address.

For password policy, we have set that password should be of minimum length 8, should be alphanumeric with upper case and lower case characters.

Authentication with AWS Cognito - Password Policy

Most of the other settings we will choose as default. For App Clients Step, we will choose username and password-based authentication for Auth Flow.

Authentication with AWS Cognito - App Clients

Application Client Configuration

Once we create a user pool, we will continue to step through the next set of configurations and that includes the Application Client. In this configuration, we configure our application that will be using the user pool.

We need to enable identity provider and Cognito User Pool.

Authentication with AWS Cognito - App Client Settings

Configuring Federation Identity Provider

As mentioned previously, we will also configure a SAML Identity Provider for authentication. In this case, the flow will be application -> AWS Cognito -> SAML Identity Provider.

Configure Application in Okta

For this demo, we will use Okta as SAML Identity Provider. Log in to Okta Administrator console , configure a new application for SAML Integration as below:

Configure SAML Okta

On the next step, provide Single Sign On URL and this will be https://{yourcustomdomainfromcognito}.auth.{awszone}.amazoncognito.com. We also need to provide Audience URI (SP Entity ID). In this case, Cognito will act as Service Provider to Okta.

 Okta SAML Settings

We also need to configure Attribute Statements as shown above. Those are the attributes we have configured in our Cognito User Attributes – email, given_name, family_name.

Basically, make sure that the user you plan to use is in Okta Users Directory. It will be the same user that we will need to be either configured in Cognito User Pool or created through sign-up.

Configure Federated Identity Provider in Cognito

  • In our user pool configuration, select Federated Identity Provider.
  • Choose the option of SAML and Cognito will show you to upload a metadata file or metadata URL for Identity Provider. In our case, it will be https://dev-19753289.okta.com/app/exkmt322q1Kl15Rsk5d6/sso/saml/metadata.
  • Type a name for Federated Identity Provider and Save the changes.

Therefore, if we go back to App Client Settings, we should see an option to select Federated Identity Provider. With the new configuration, our settings will look like below:

Cognito Federated Identity Provider

Anyhow, make sure the user attributes(given_name, family_name, and email) in Cognito are editable.

However, we have completed our configuration. Now we can show the demo.

Demo of Spring Boot Application Authentication with AWS Cognito

Now if I run my application and access it at https://localhost:8743/, I will see the following screen to select an identity provider to authenticate with:

Home Realm Discovery Cognito

Nevertheless, we can use Okta or Cognito User Pool to log in.

SAML Flow

  1. User accesses the application that sends Authorization Code Flow OAuth request to Cognito
  2. Cognito sends SAML Request to Okta.
  3. The user enters credentials on the okta login screen.
  4. Okta sends the SAML response back to Cognito at endpoint https://{customdomain}.auth.{awszone}.amazoncognito.com/saml2/idpresponse
  5. Cognito processes the saml response and generates auth code and response back to the application.
  6. The application uses auth code to get token from Cognito and authenticates the user on verification of token.

Above all, after successful authentication, the user will see

Okta SAML Authentication

Instead of Okta, we had chosen Cognito login on the same Home Realm Discovery screen, we will see the following after successful authentication:

Cognito Successful Authentication

Conclusion

In this post, I showed how we can use AWS Cognito for authentication with Spring Boot application. Moreover, Cognito helps in configuring users as well as removing boilerplate code of security flows.

In conclusion, if you have not bought my ebook Simplifying Spring Security, it is available here.

2 thoughts on “Example of Spring Boot Application Authentication with AWS Cognito

    1. yogesh.mali@gmail.com Post author

      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.security.core.Authentication;
      import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
      import org.springframework.stereotype.Component;

      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;

      @Component
      public class CustomLogoutSuccessHandler implements LogoutSuccessHandler
      {
      public static final Logger LOGGER = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);

      @Override
      public void onLogoutSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
      {
      LOGGER.info("Enter >> onLogoutSuccess()");
      if (authentication != null && authentication.getDetails() != null)
      {
      LOGGER.info("authentication info found.");
      try
      {
      request.getSession().invalidate();
      }
      catch (Exception e)
      {
      LOGGER.debug("Exception during logout " + e.getStackTrace());
      throw new RuntimeException("Could not logout due to " + e.getLocalizedMessage());
      }
      }
      response.setStatus(HttpServletResponse.SC_OK);
      LOGGER.info("Exit << onLogoutSuccess()"); } }

      Here it is

Comments are closed.