Thymeleaf OAuth2 Login with Spring Security and AWS Cognito

Last Updated:  February 16, 2021 | Published: June 13, 2020

Securing your frontend application with a login and managing a user pool is something you can either write for yourself or use an external identity provider for. If you want to move fast with your prototype you usually pick the second option and search for an OpenID Connect (OIDC) and OAuth2 compliant identity provider. AWS offers its own solution for this: AWS Cognito. With this blog post, I'll guide you through every required step to configure AWS Cognito for your Spring Boot application using Spring Security to secure a Thymeleaf application with an OAuth2 login.

UPDATE: There is now the second part of this article available which covers the OIDC logout.

Spring Boot setup with Thymeleaf and Spring Security

The demo application uses Maven, Java 11, and Spring Boot 2.3.0. Besides the Spring Boot Starters for Web and Thymeleaf, we need the starters for Spring Security and OAuth2 Client:

The application exposes one view where users can log in using OAuth2 (OIDC to be more specific) and AWS Cognito to see messages based on their authentication:

Thymeleaf application without authentication

… and if a user logs in:

Thymeleaf application with authentication

Required AWS Cognito set up in the AWS console

Setting up AWS Cognito for this OAuth2 login with Spring Security requires some configuration steps in the AWS console.

First, log in to your AWS account and search for the AWS Cognito service:

AWS Cognito entry page

Ensure you are in the correct AWS region you want to create the service for (I'm using eu-central-1). Next, click on Manage User Pools and create a new one:

Create a new AWS Cognito User pool

You can give it any name you want. Continuing with Review defaults is enough for this user pool creation.

Once you created the user pools with the defaults, you can add app clients (left side menu: General Settings -> App Clients). Our Spring Boot application will act as a client. Therefore configure the client like the following:

Create app client AWS Cognito

The pre-selected configuration for the app client is fine. Just ensure you add the app client name. Once you created the client, store the client id and client secret somewhere e.g. as an environment variable (we need this later on for Spring Security).

As a next step, we configure the app client settings (left side menu: App Integration -> App Client Settings) as the following App client settings OAuth2 AWS Cognito

It's important to add the correct callback URL. With Spring Security the path is /login/oauth2/code/{nameOfTheRegistration}. For local development, the localhost URL is all we need. You can add multiple (e.g your production URL) as a comma-separated list here. Ensure you select Authorization code grant and allow email and openid scope.

To use the sign-up and login-in page hosted by AWS Cognito, we have to configure a domain name for it (left side menu: App integration -> Domain Name):

Configure AWS Domain Name

As a final step, we can add the first user to our user pool. Go to General Settings -> Users and groups and add one.

To continue with the next section, ensure you have …

  • the client id and client secret
  • the AWS region you used
  • your pool id (you can retrieve this inside General Settings)

… ready and at least one user inside your user pool.

Configuring AWS Cognito as an identity provider

Next comes the Spring Security configuration. Our Spring Boot application acts as an OAuth2 client and Spring Security provides a convenient way to configure this.

Open either your application.yml or application.properties file and add the following section:

The configuration property spring.security.oauth2.client.registration contains a map of OAuth2 client registrations (there might be multiple for one application). In our example, we give the AWS Cognito registration the name cognito. Hence our callback URL is /login/oauth2/code/cognito.

Besides the configuration for scope, redirectUriTemplate, and clientName, we now need both the clientId and the clientSecret from the previous chapter. I'm using an environment variable to store both values and refer to them using ${NAME_OF_ENV_VARIABLE}.

Inside spring.security.oauth2.client.provider we now add information about the identity provider. The name of the provider has to match the name of the registration. As we are using OpenID Connect, we can add the OpenID Connect discovery endpoint to issuerUri. Make sure to construct the correct URI for your setup by replacing the region and your pool id.

With userNameAttribute we give Spring Security a hint which attribute to use for the username after calling the user info endpoint. Depending on your Cognito setup this is either cognito:username or username.

Next comes the actual security configuration for our application:

The configuration above ensures to allow access to our page "/" for everyone, enables CSRF, OAuth2 Login, and configures the application to redirect the user after he logs out to the entry page.

Secure Thymeleaf application with OAuth2 login

Now we are really close to having a working OAuth2 login with Thymeleaf and AWS Cognito using Spring Security.

Three things are missing:

  • a login mechanism
  • an ability to logout
  • securing parts of the application only logged-in users

With the help of thymeleaf-extras-springsecurity5 we get the integration to Spring Security. This allows us to use well-known Spring Security expressions (think hasRole("XYZ") or isAuthenticated()) inside our template.

Let's take a look at the Thymeleaf template skeleton including the login and logout functionality:

Besides the standard Thymeleaf th namespace, we are including also the sec namespace. We'll use sec:authorize for our HTML elements to conditionally render them based on Spring Security expressions.

First, we need a way for users to log in to our application. Therefore we can add a link (HTTP GET) to /oauth2/authorization/cognito whenever an anonymous user enters the page. When clicking this link, Spring Security takes care of redirecting the user to AWS Cognito and fetching the access and ID token in the background (when the user enters correct credentials).

Next, we need the logout functionality for users. This should be only available if an authenticated user enters the page.  For the logout part, we are using Spring Security's logout mechanism and make an HTTP POST to /logout.

Furthermore, let's add more examples for the Thymeleaf and Spring Security integration to conditionally render parts of the page:

At the controller level, you can also request authentication information about the user and add different attributes to the Model e.g. based on a role:

Testing the OAuth2 protection for Thymeleaf

Most tutorials end here. Let's go a step further and take a look at how to test this application.

Besides the basic Spring Boot Starter Test, I'm including a dependency to effectively test Spring Boot applications in conjunction with Spring Security:

With @WebMvcTest we can easily test our MVC controller in isolation. This allows us to write tests without having access to AWS and focus on testing our endpoint.

Using this annotation an auto-configured MockMvc instance is available and by default, our Spring Security configuration is also included.

A first test might verify an anonymous user can access the page (as our Spring Security config allows this) and the MVC model contains the message attribute:

Further tests can ensure the result for different OIDC login scenarios (e.g. different claims or roles).

For this we can use the oidcLogin() method of the Spring Security test dependency and model different users:

Summary

We are now at the end of this tutorial. Let's summarize the required steps to add an OAuth2 Login with AWS Cognito for a Thymeleaf application:

  • create a user pool in the AWS console
  • create and configure an OAuth2 client for the user pool
  • configure the Spring Boot application to act as an OAuth2 client
  • secure parts of the Thyemleaf application using the extras dependency for Spring Security

While this tutorial focussed solely on using AWS Cognito, the basic setup works with any OpenID Connect and OAuth2 compliant identity provider. If you use e.g. Keycloak you have to adjust the OAuth2 client registration and provider while the rest of the application works as expected.

UPDATE: There is now the second part of this article available which covers the OIDC logout.

You can find further Spring Boot and AWS related tutorials on my blog and the application for this guide on GitHub.

Have fun securing your Thymeleaf applications with OAuth2 login using AWS Cognito,

Phil

>