OIDC Logout With AWS Cognito and Spring Security

Last Updated:  February 17, 2021 | Published: February 16, 2021

With one of the previous blog posts, we configured a Thymeleaf Spring Boot application for an OAuth 2 Login with Spring Security and AWS Cognito. While this article focussed on the setup and login mechanism, the logout functionality was only half-way implemented. Our end-users are still logged in at the identity provider. Let's adjust the application for an additional logout at AWS Cognito using Spring Security.

Spring Boot Project Setup

This blog post builds on top of  Thymeleaf OAuth2 Login with Spring Security and AWS Cognito. Make sure to first read through the existing article to get a basic understanding of the application set up as we're not going to cover the required setup steps. This blog post focuses solely on the logout functionality.

As part of the GitHub repository, you'll find a sample CloudFormation stack to create and configure the AWS Cognito instance.

Why Do We Need To Configure the Logout?

With OpenID Connect and Spring Security, our users will have two sessions: one application-specific and one at the identity provider. Whenever a user signs out, Spring Security (by default) will invalidate the first session, and our users would still be logged in at the identity provider.

Every subsequent login attempt will automatically log them into our application without asking for their credentials (expect their identity provider's session expired). For some applications, this might be a feature. For other use cases, we might want to fully log out our users so that they can easily switch accounts.

The OIDC specification defines how a client can perform the logout at the identity provider: OpenID Connect RP-Initiated Logout 1.0 (currently in draft mode):

This specification complements the OpenID Connect Core 1.0 [OpenID.Core] specification by enabling the Relying Party to request that an End-User be logged out by the OpenID Provider.

The term Relying Party is just a different word for the OAuth 2.0 Client: the Spring Boot application in our example. With End-User, the specification refers to any user of our application that can log in. The OpenID provider for our setup is AWS Cognito.

Let's see how Spring Security can help us here.

Doesn't Spring Security Support the “Full Logout”?

Yes, it does.

But …

… only for identity providers that are following the OpenID Connect RP-Initiated Logout specification. As this spec is currently in draft mode, not every provider already implements it.

In case we're working with an identity provider that supports it (e.g. Keycloak), adding this logout mechanism requires a little adjustment for our WebSecurityConfig. Spring Security provides a logout success handler for this scenario: OidcClientInitiatedLogoutSuccessHandler

Unfortunately, AWS Cognito doesn't implement the RP-Initiated Logout specification (yet). However, AWS Cognito offers a proprietary logout mechanism.

AWS Cognito Logout Mechanism

AWS Cognito defines a LOGOUT Endpoint that we can use to sign out the end-user. This endpoint expects two request parameters and only support HTTPS and GET requests:

When using an AWS Cognito hosted signup page, the logout endpoint has the following structure https://<DOMAIN_PREFIX>.auth.<AWS_REGION>.amazoncognito.com/logout.

Unfortunately, AWS Cognito doesn't expose this logout URL as part of the OAuth 2.0 discovery endpoint. Identity providers that are compatible with the RP-Initiated specification return a end_session_endpoint. As we don't have this attribute available for AWS Cognito, we have to construct the URL on our own, e.g., passing all dynamic parts of the URL to our application or defining it as a CloudFormation output:

Let's take a look at the two parameters we pass to this endpoint.

The client_id refers to the OAuth 2.0 client ID of our Spring Boot application. We already configure this ID as part of our application.yml file, and hence our application can inject it.

Next, the logout_uri defines a URL that AWS Cognito will redirect the user to after signing them out. We can choose any target that we want our users to land after logging out, e.g., a public welcome page of our application.

There's only one requirement for this redirect. The URL has to match one of the valid logout URLs for our AWS Cognito UserPoolClient . Otherwise, the logout mechanism will fail:

With this information about the AWS Cognito Logout endpoint, let's see how we can configure Spring Security to invoke it as part of the logout procedure.

Supporting AWS Cognito's Logout With Spring Security

Due to Spring Security's great extendability, we can define our own LogoutHandler. Spring Security will invoke this handler as part of its own logout process (invalidating the HTTP session and clearing the SecurityContextHolder).

By default, this logout mechanism is mapped to /logout. We don't need an own controller mapping for this endpoint as Spring Security will handle any incoming HTTP requests to this endpoint.

As AWS Cognito expects an HTTPS request for the logout, we can extend Spring Security's SimpleUrlLogoutSUccessHandler that is also used by the OidcClientInitiatedLogoutSuccessHandler.

What's left is to override the determineTargetUrl and create the URL that Spring Security will invoke as part of the logout process:

This basic implementation expects both the clientId and logoutUrl as parameters. We're redirecting the user to the base URL of our application. We could also make this configurable.

It's possible to determine both theclientIdand parts of thelogoutUrl by injecting the ClientRegistrationRepository and retrieving the OAuth 2.0 Metadata. However, this includes String manipulation on various attributes of the metadata and makes it a little bit more brittle.

The most intuitive approach is to inject both values with @Value and configure them as part of our application.yml or as an environment variable. The clientId is already part of our Spring Security OAuth 2.0 setup, and hence only the logoutUrl is left to configure:

Configure The Logout With Our Custom Logout Success Handler

Putting it all together, we can add our CognitoOidcLogoutSuccessHandler as the logoutSuccessHandler when specifying the security setup for our application:

With this logout handler in place, Spring Security ensures to call the AWS Cognito logout endpoint as part of its logout mechanism. Every subsequent login attempt will display the AWS Cognito login form to our users. They have to enter their credentials again and can now seamlessly switch accounts.

We don't need any further adjustments to our Thymeleaf view and can rely on the existing logout button:

For more deep-dive on AWS Cognito, Spring Security, and Spring Boot, please take a look at the Stratospheric Book, which I'm co-authoring. For the book's sample application, we're using AWS Cognito for the login and registration functionality.

Furthermore, this book will teach you everything you need to know to get your Spring Boot application into production with AWS. Among other things, this includes:

  • AWS CloudFormation introduction
  • Deep dive into the AWS CDK (Cloud Development Kit)
  • Build a continuous deployment pipeline
  • Connect to various AWS Services (RDS, SES, SQS, etc.) with Spring Boot using Spring Cloud AWS
  • … and much more

If you’re interested in learning about building applications with Spring Boot and AWS from top to bottom, make sure to check it out!

The source code for this OIDC Logout example with AWS Cognito and Spring Security is available on GitHub.

Have fun securing your application with AWS Cognito,

Philip

>