Spring Boot OAuth2 SSO Implementation with AWS Cognito

  • In this article we will learn how to allow users for OAuth2 SSO (Single Sign On) using AWS (Amazon Web Services) Cognito.
  • SSO (Single Sign On) is where a user logs in with a single set of credentials (username/password) to gain access to multiple software systems.
  • OAuth is a protocol with which a 3rd-party app can access your data stored in another website without your account and password.
  • For more information on OAuth 2, you may go through the following reference: https://oauth.net/2/

Here, I am going to use AWS Cognito for OAuth2 SSO as below.

AWS Cognito for OAuth2 SSO flow diagram
What is AWS Cognito?

In simple words AWS Cognito is a service provided by Amazon that allows developers to provide authentication, authorization and user management for their web and mobile applications. With AWS Cognito, we can even configure it to, where users can login through third parties such as Facebook, Amazon, Google or Apple.

What are AWS Cognito main components?

AWS Cognito has two main components for supporting authentication/authorization flows – User Pools and Identity Pools.

What is User Pools in AWS Cognito?

A User Pool is a User Directory in AWS Cognito for hadling user registration, sign-in, authentication and account recovery etc in our web application.

What is Identity Pools in AWS Cognito?

It provides temporary AWS credentials for users that are authenticated or even unauthenticated.

What is the difference btween a User Pool and an Identity Pool?

A User Pool is a User Directory in AWS Cognito. User Pools created for authentication (identify verification). While, Identity Pools created for authorization (access control). We can use Identity Pools to create unique identities for the users and give them access other AWS services, such as Amazon S3, DynamoDB, etc. For example, suppose we have an application that allows a user to upload a file to Amazon S3 (file storage). In order to allow that authenticated user to use other AWS Services such as S3 on your application, we need to make use of Identity Pools (Federated Identity Pools).

How to create a User Pool in AWS Cognito?

Step 1. Go to the Amazon Cognito console. You may be prompted for your AWS credentials.
Step 2. Choose Manage User Pools.
Step 3. Choose Create a User Pool.
Step 4. Provide a name for the User Pool and choose Review defaults to Save the name.
Step 5. On the Review page, choose Create pool.

Technology Stack

Java 8
Maven 3.5
AWS Cognito Configuration
Spring Boot 2.4.1
Thymeleaf
Spring OAuth2 Client

What is AWS Cognito? How to Configure AWS Cognito?

In summary; the Cognito User Pool stores all your users which then plugs into your Cognito Identity Pool which can give users access to other AWS services. In simple words AWS Cognito is a service provided by Amazon that allows developers to provide authentication, authorization and user management for their web and mobile applications.

First you need to login to AWS Console [https://console.aws.amazon.com/console/home]. You may also want to check whether you want to login as Root user or IAM user. In the next page you need to input password.

 AWS Console Login screen

You need to select Cognito in the AWS management Console as below:

AWS management Console Screen

We need to configure user in the AWS Console as below.

  1. We need to create user pool using Create a user pool option. Next we need to configure it to integrate this user pool with identity pool.
  2. We need to create IAM role and add a specific access. Then you have to create user group and map it to new IAM role.

Microservice : Spring Boot Application Setting in AWS

This Spring Boot application will act as a client therefore you need to add app client from the option General Settings -> App Client.

Note: While configuring the client application we need to:

  1. Enter the App Client Name.
  2. Refresh token expiration (in days).
  3. Check the box Generate client secret.
  4. Enable lambda trigger based custom authentication (ALLOW_CUSTOM_AUTH).
  5. Enable SRP (secure remote password) protocol based authentication (ALLOW_USER_SRP_AUTH).
  6. Enable refresh token based authentication (ALLOW_REFRESH_TOKEN_AUTH).
  7. Then select the radio Enabled (Recommended).
  8. Finally click on Create app client button.

How to Configure Callback and Sign out URLs

The callback and sign out URLs are important in the above configuration.

Then you need to configure app client settings by going from the menu App Integration -> App Client Settings. The configuration should be something similar to the below image:

Configure Callback and Sign out URLs - App integration

To use the sign up and login page hosted by AWS Cognito, you need to configure a domain name for it from the left side menu: App integration -> Domain Name.

How to Configure Callback and Sign out URLs

Microservice: Spring Boot Application Structure

High level application structure would be looked like as below.

High level application structure for a spring boot application

Maven File: pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.4.1</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-boot-oauth2-sso-aws-cognito</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-oauth2-sso-aws-cognito</name>
	<description>Project for Spring Boot AWS Cognito Integration</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-oauth2-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Microservice: Application Configuration (application.yaml)

Application Configuration file application.yaml will be looked like as below.

server:
  port: 8020

spring:
  security:
    oauth2:
      client:
        registration:
          cognito:
            clientId: <client id>
            clientSecret: <client secret>
            scope: openid, email
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/cognito"
            clientName: <client name>
        provider:
          cognito:
            user-info-uri: <user info URL>
            jwk-set-uri: <jwks URL>
            token-uri: <token URL>
            authorization-uri: <authorize URL>
            userNameAttribute: sub

Note: clientId, clientSecret, clientName and etc. need to be filled with correct values from AWS console.

Spring Boot: Security Configuration

  • The Java based Spring security configuration class need to be added, that permits access to base URL or root URL (“/”) and asks for authentication for any other URL.
  • The logout URL is configured in the AWS cognito configurations.
package com.the.basic.tech.info.auth2awscognito;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class OAuth2SsoSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().and()
				.authorizeRequests(authorize -> authorize.mvcMatchers("/").permitAll().anyRequest().authenticated())
				.oauth2Login().and().logout().logoutUrl("/logout").logoutSuccessUrl("/");
	}

}

Spring Boot: How to add Rest Controller for exposing Rest API

In our application, the controller is included in the main class that acts as a Spring boot application responsible for the deployment of the app in the embedded Tomcat server.

package com.the.basic.tech.info.auth2awscognito;

import java.util.stream.Collectors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.Authentication;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@SpringBootApplication
public class SpringBootOauth2SsoAwsCognitoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootOauth2SsoAwsCognitoApplication.class, args);
	}
	
	@GetMapping("/")
	public String home(Model model, Authentication authentication) {
		if (authentication != null && authentication.isAuthenticated()) {
			model.addAttribute("name", authentication.getName());
			model.addAttribute("principal", authentication.getPrincipal());
			model.addAttribute("authorities", authentication.getAuthorities().stream().map(a -> a.getAuthority())
					.collect(Collectors.joining(",")));
		}

		model.addAttribute("message", "Spring Boot OAuth2 SSO with AWS Cognito");

		return "index";
	}

}

Spring Boot : How to add View Template

This the Thymeleaf template file index.html which is kept under src/main/resources/templates folder. This page shows the authentication details if user was authenticated successfully otherwise it shows login page.

<!DOCTYPE html>
<html lang="en"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
      xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <title>Spring Boot OAuth2 SSO Login AWS Cognito</title>
</head>
<body>
<div>
  <div>
    <h1 th:text="${message}"></h1>

    <div sec:authorize="isAuthenticated()">
      <p>Hello, <strong th:text="${#authentication.name}"></strong>!</p>
      <p>Your Authorities: <strong th:each="authority : ${#authentication.authorities}"><span
        th:text="${authority.authority} + ' '"></span></strong></p>
    </div>
    
    <div th:if="${message}">
      The message is: <strong th:text="${message}"></strong>
    </div>

    <div th:if="${name}">
      The name is: <strong th:text="${name}"></strong>
    </div>
    
    <!--<div th:if="${principal}" class="alert alert-primary" role="alert">
      The principal is: <strong th:text="${principal}"></strong>
    </div>-->
    
    <div th:if="${authorities}" class="alert alert-primary" role="alert">
      The authorities are: <strong th:text="${authorities}"></strong>
    </div>

    <div sec:authorize="isAnonymous()">
      <a th:href="@{/oauth2/authorization/cognito}">
        Login with AWS Cognito
      </a>
    </div>

    <div sec:authorize="isAuthenticated()">
      <form method="post" th:action="@{/logout}">
        <input type="submit" value="Logout"/>
      </form>
    </div>
  </div>
</div>
</body>
</html>
Thymeleaf template view

Microservice : Deploy, Run & Testing the Application

Run the following maven command for build, deploy and run the Microservice over embadded tomcat container in local.

mvn spring-boot:run
D:\development\spring-boot-oauth2-sso-aws-cognito>mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-boot-oauth2-sso-aws-cognito 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.4.1:run (default-cli) > test-compile @ spring-boot-oauth2-sso-aws-cognito >>>
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-boot-oauth2-sso-aws-cognito ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] Copying 1 resource
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-boot-oauth2-sso-aws-cognito ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\development\spring-boot-oauth2-sso-aws-cognito\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ spring-boot-oauth2-sso-aws-cognito ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory D:\development\spring-boot-oauth2-sso-aws-cognito\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-boot-oauth2-sso-aws-cognito ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\development\spring-boot-oauth2-sso-aws-cognito\target\test-classes
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.4.1:run (default-cli) < test-compile @ spring-boot-oauth2-sso-aws-cognito <<<
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.4.1:run (default-cli) @ spring-boot-oauth2-sso-aws-cognito ---
[INFO] Attaching agents: []

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.1)

2021-08-17 23:58:57.544  INFO 24924 --- [  restartedMain] SpringBootOauth2SsoAwsCognitoApplication : Starting SpringBootOauth2SsoAwsCognitoApplication in D:\development\spring-boot-oauth2-sso-aws-cognito)
2021-08-17 23:58:57.550  INFO 24924 --- [  restartedMain] SpringBootOauth2SsoAwsCognitoApplication : No active profile set, falling back to default profiles: default
2021-08-17 23:58:57.651  INFO 24924 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2021-08-17 23:58:57.651  INFO 24924 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2021-08-17 23:59:00.025  INFO 24924 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8020 (http)
2021-08-17 23:59:00.050  INFO 24924 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-08-17 23:59:00.051  INFO 24924 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-08-17 23:59:00.165  INFO 24924 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-08-17 23:59:00.165  INFO 24924 --- [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2484 ms
2021-08-17 23:59:00.591  INFO 24924 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-08-17 23:59:00.745  WARN 24924 --- [  restartedMain] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
2021-08-17 23:59:00.950  INFO 24924 --- [  restartedMain] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@d71a72, org.springframework.security.web.context.SecurityContextPersistenceFilter@b38fcf, org.springframework.security.web.header.HeaderWriterFilter@3d9968, org.springframework.security.web.csrf.CsrfFilter@1ff618b, org.springframework.security.web.authentication.logout.LogoutFilter@1b75d24, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@13cd589, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@1a0954c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10b9318, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@184290f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5c8a8a, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@14aa1dd, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@125d974, org.springframework.security.web.session.SessionManagementFilter@988c1c, org.springframework.security.web.access.ExceptionTranslationFilter@18218bd, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1d73efe]
2021-08-17 23:59:01.017  INFO 24924 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2021-08-17 23:59:01.081  INFO 24924 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8020 (http) with context path ''
2021-08-17 23:59:01.101  INFO 24924 --- [  restartedMain] SpringBootOauth2SsoAwsCognitoApplication : Started SpringBootOauth2SsoAwsCognitoApplication in 4.367 seconds (JVM running for 4.951)

Hit On Broswer [http://localhost:8020]

Once we will hit http://localhost:8020 on browser we will be redirected to the below login page which contains “Login with AWS Cognito” link as below.

AWS Spring Boot OAuth2 SSO with Cognito

Login with Amazon Cognito

Once we will click on Login with Amazon Cognito link, it would be redirected to the following login page:

Login with Amazon Cognito Screen

Now, we can use our gmail login credentials to authenticate the account or we can use login credentials for our open id to authenticate the account. I used my gmail id by clicking on Sign in with Google to authenticate the system.

Once you are success fully authenticated you will be redirected to the home page with the following output:

Amazon Cognito screen after logged in

Note : As per requirement we can implement or redirect the user to different page after successful authentication.

That’s all about Spring Boot OAuth2 SSO with AWS Cognito Integration .

Thank you for reading. Have a great day 🙂