If I have a Spring Boot 3 based application that utilizes Spring Security and Spring WebFlux and is already setup to authenticate and authorize users via IdP1, then how do I enhance this application to authenticate and authorize a subset of users via IdP2?
Config. example -
spring:
security:
oauth2:
client:
registration:
idp1:
client-id: idp1client
client-secret: idp1secret
scope: openid, profile, email
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
idp2:
client-id: idp2client
client-secret: idp2secret
scope: openid, profile, email
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
provider:
idp1:
authorization-uri: http://idp1/auth
token-uri: http://idp1/token
idp2:
authorization-uri: http://idp2/auth
token-uri: http://idp2/token
Flows via IdP1 work fine. IdP2 requires customization of the token request (additional API calls to get some fields etc.), for generating an access token once an authorization code has been generated using the above configuration. This is what I am unable to figure out and the online documentation feels a bit dense at - https://docs.spring.io/spring-security/reference/reactive/oauth2/index.html - with regards to which beans need to customized for IdP2 to work as expected.
Thanks in advance!
If one of these authorization servers allows it, you might use it to federate identities from others (Auth0, Amazon Cognito, and many others include login with... feature). If none supports federation for all others, you might add one just for that (Keycloak and Spring Authorization Server are frequently used solutions). The federating authorization server will be the only one configured in your clients and resource servers.
This option with a "master" authorization server greatly eases user roles management, OAuth2 clients declaration, Spring configuration, etc... I suspect Spring Security was designed with this scenario in mind. Implementations with multiple registrations configured for authorization code are hackish, especially if you intend to have users logged-in against several providers at the same time.
ServerOAuth2AuthorizationRequestResolver
Otherwise you can keep a provider in your conf for each authorization server (and an authorization_code registration for each provider on which you want the users to be able to login). Define your authorization request resolver to build different requests for each registration like I do here. Inject it when configuring the filter-chain:
@Component
static class MyServerOAuth2AuthorizationRequestResolver implements ServerOAuth2AuthorizationRequestResolver {
...
}
@Bean
SecurityWebFilterChain clientFilterChain(
ServerHttpSecurity http,
ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver) {
http.oauth2Login(oauth2 -> {
oauth2.authorizationRequestResolver(authorizationRequestResolver);
});
...
}
spring-addons-starter-oidc
If you don't feel like writing (and maintain) such an authorization request resolver yourself, have a look at this starter I wrote, it supports custom authorization request parameters (like the audience
required by Auth0), on a per registration basis, with just application properties (no Java conf needed).
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-oidc</artifactId>
<version>7.8.8</version>
</dependency>
spring:
security:
oauth2:
client:
provider:
auth0:
issuer-uri: ${auth0-issuer}
idp2:
issuer-uri: ${idp2-issuer}
registration:
login-with-auth0:
provider: auth0
client-id: ${auth0-client-id}
client-secret: ${auth0-client-secret}
authorization-grant-type: authorization_code
scope: openid, profile, email, offline_access
login-with-idp2:
provider: idp2
client-id: ${idp2-client-id}
client-secret: ${idp2-client-secret}
authorization-grant-type: authorization_code
scope: openid, profile, email, offline_access
com:
c4-soft:
springaddons:
oidc:
ops:
- iss: ${auth0-issuer}
authorities:
- path: $['https://c4-soft.com/user']['roles']
- iss: ${idp2-issuer}
authorities:
- path: $.realm_access.roles
client:
client-uri: ${gateway-uri}
security-matchers:
- /login/**
- /oauth2/**
- /logout/**
- /v1/**
permit-all:
- /login/**
- /oauth2/**
- /v1/**
authorization-params:
auth0:
audience: change-me
oauth2-logout:
auth0:
uri: ${auth0-issuer}v2/logout
client-id-request-param: client_id
post-logout-uri-request-param: returnTo
// No Spring Security Java (or XML) conf is needed at all!
This sample assumes that
auth0
requires an audience
parameter with the authorization request, and its OpenID configuration does not include an end_session_endpoint
idp2
is fully compliant with OIDC (OpenID configuration includes an end_session_endpoint
), does not require any custom argument with authorization request, and stores user roles under realm_access.roles
(picked what Keycloak does for illustration purposes).Note that much more than what is displayed above can be configured (JSON path to username claim, adding prefix or changing case of authorities, post login / logout URIs, cookie based CSRF protection, ...). See the README already linked above for details