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

  • Hey,

    Great articles, thanks.

    I don’t know if it has to do with my cognito configuration, but my username attribute is “cognito:username” instead of just “username”.

    after I change it in the configuration, I got it working.

    Thanks again.

    • Hey Thiago,

      thanks for the feedback and the hint. Not sure what’s different in your setup. I’ll double-check with a running example and update the article if required.

      Kind regards,
      Phil

      • Hi Phil,
        The username is the default attribute of cognito. Only custom attribute need to be prefixed with ‘custom:’. I tried your example with cognito:username it fails badly. The correct one is ‘username’ only and you have cognito as provider.

        In your example you have demonstrated with localhost. With the same setup and having a url say example: https://www.mynameisgovind.net“. If you try to replace localhost:8080 with the new url and deploy it in aws, it does not work.

        The domain is already registered, userpool authorization is updated to https://www.mynameisgovind.net/login/oauth2/code/cognito.

        The AWS API Gateway routes to the index page. When you click on the ‘Log in with Amazon Cognito’ in the index page. The spring security login is displayed, you sign and cognito authenticates.

        The cognito issues the authorization code but it does not route to the ‘https://www.mynameisgovind.net/’. This works good with localhost:8080 once authenticated you are routed back to localhost:8080.

        Would be great if you could add in MVC to capture the oauth2 code and have it displayed.

        thanks
        Govind

        • Hey Govind,

          thanks for your comment.

          I currently have a Spring Boot application in production that uses the setup as described in the blog post. Replacing every localhost with the domain of the application worked for me.

          However, I’m not using the AWS API Gateway for my setup and rather plain ECS with a Load Balancer to access the Spring MVC app.

          Can you elaborate a little bit more about the AWS setup where the login with Cognito does not work?

          Have a nice day,
          Philip

          • Hi Philip,

            We use containers (ecs) and aws api gateway to expose the end point or web server.

            On the local machine the aws-elb routes to ‘localhost’ but when it is executed on aws, the elb does not route it to the actual domain https://www.mynameisgovind.net/

            Getting 302 error – redirect issue. The elb is not routing it to the https://www.mynameisgovind.net/ where as for localhost:8080 but default its redirecting.

            regards
            govind

          • I’ve not much experience with the API Gateway of AWS but I assume there must be some access logs available to debug and trace what is happening behind the scenes?

            Can you try to open the network tab when you click the login with Cognito button to see the full URL that also contains the redirect URL?

            My application is also running within ECS and once you run with more than one instance I had to configure sticky sessions for the ELB.

          • we did trouble shoot with AWS engineer, the redirect is happening to elb (elastic load balancer) which then times out. The trace indicates the authentication is successful. After the authentication the “spring mvc” is not redirecting properly to the index.html page.

          • oh okay. Then I guess the next step would be to create an issue on the Spring Web MVC or Spring Security repository, as I might not be of any further help.

  • When you logout and then select Log In With Amazon Cognito you are no longer prompted for credentials and instead just taken to the authenticated page. Would have assumed that logout would result in being asked for credentials to re-authenticate ?

    Thanks

    • Hi Graeme,

      that’s true. The reason for this is that the page from AWS Cognito where you log in also has a remember me the functionality. Whenever you now re-enter this login dialog, you are automatically signed in. I assume this can be configured on the AWS side.

  • Hi,

    I am facing issue in redirect.
    Error : [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for “https://roarapps.auth.ap-south-1.amazoncognito.com/oauth2/token”: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.

    Any Solution?

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >