'How to onfigure Spring Boot to authenticate Web-app users and REST clients using AWS Cognito (OAuth2/OIDC)
I need to configure a Spring Boot server to authenticate web-users and REST clients using AWS Cognito user-pool:
- Interactive/Web users that are using the ReachJS frontend should be redirected to Cognito for authentication, and are redirected back once the user's credentials are verified.
- Other machines using the server's REST API directly should get a token from Cognito and send it to my server as the
Authorization: Bearer ...header.
Questions are:
- How to configure spring to authenticate using Cognito
- How do you make spring supporting these two distinct types of authentication simultaneously
Solution 1:[1]
Overview
Lets start with terminology:
- IDP (Identity Provider) is a 3rd party providing user management and authentication service, AWS Cognito in my case.
- Authentication of interactive/web users by redirecting them to the IDP is referred to in OAuth2/OIDC as the "Authorization Code Grant Flow".
- Client sending JWT tokent to a REST API is known as the "Client Credentials Flow".
Spring's spring-security-oauth2-client module is responsible for the "Authorization Code Grant Flow" and the spring-security-oauth2-resource-server module is responsible for the "Client Credentials Flow".
In order to use both flows/methods simultaneously, we need to tell spring how do determine what authentication method to use with an incoming HTTP request.
As explained in https://stackoverflow.com/a/64752665/2692895, this can be done by looking for the Authorization: bearer ... header:
- If the request includes the
Authorizationheader, assume its a REST client and use the "Client Credentials Flow". - Else, its an interactive user, redirect to Cognito if not already authenticated.
Dependencies
I'm using Spring-Boot 2.6.6 (Spring 5.6.2).
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
External Configuration - application.yaml
spring:
security:
oauth2:
# Interactive/web users authentication
client:
registration:
cognito:
clientId: ${COGNITO_CLIENT_ID}
clientSecret: ${COGNITO_CLIENT_SECRET}
scope: openid
clientName: ${CLIENT_APP_NAME}
provider:
cognito:
issuerUri: https://cognito-idp.eu-central-1.amazonaws.com/${COGNITO_POOL_ID}
user-name-attribute: email
# REST API authentication
resourceserver:
jwt:
issuer-uri: https://cognito-idp.eu-central-1.amazonaws.com/${COGNITO_POOL_ID}
Spring Security Configuration
Interactive/web users authentication:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
// Needed for method access control via the @Secured annotation
prePostEnabled = true,
jsr250Enabled = true,
securedEnabled = true
)
@Profile({"cognito"})
@Order(2)
public class CognitoSecurityConfiguration extends WebSecurityConfigurerAdapter {
@SneakyThrows
@Override
protected void configure(HttpSecurity http) {
http
// TODO disable CSRF because when enabled controllers aren't initialized
// and if they are, POST are getting 403
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Client()
.and()
.logout()
.and()
.oauth2Login()
.redirectionEndpoint().baseUri("/login/oauth2/code/cognito")
.and()
;
}
}
REST clients authentication:
/**
* Allow users to use a token (id-token, jwt) instead of the interactive login.
* The token is specified as the "Authorization: Bearer ..." header.
* </p>
* To get a token, the cognito client-app needs to support USER_PASSWORD_AUTH then use the following command:
* <pre>
* aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --output json \
* --region $region --client-id $clientid --auth-parameters "USERNAME=$username,PASSWORD=$password" \
* | jq .AuthenticationResult.IdToken
* </pre>
*/
@Slf4j
@Configuration
@Profile({"cognito"})
@Order(1)
public class CognitoTokenBasedSecurityConfiguration extends WebSecurityConfigurerAdapter {
@SneakyThrows
@Override
protected void configure(HttpSecurity http) {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization"))
.authorizeRequests().anyRequest().authenticated()
.and().oauth2ResourceServer().jwt()
;
}
}
Cognito Configuration Notes
- In AWS Cognito, you need to create a user-pool and two client-applications, a "Public client" for the interactive/web users and a "Confidential client" for the token based REST clients.
- In the "Public client", make sure to define the "allowed callback URL" for all your environments (localhost, production etc), they all should be similar to http://localhost:8080/login/oauth2/code/cognito (with the correct host-name and port of course).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Michael Yakobi |
