spring-securityoauth-2.0spring-webflux

How do I setup authentication via multiple IdPs that support OAuth 2.0?


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!


Solution

  • My prefered solution: federate identities

    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 authorization servers.

    This option with a "master" authorization server greatly eases user roles management, OAuth2 clients declaration, Spring configuration, etc...

    Do It Yourself: write a 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). Just define your authorization request resolver to build different requests for each registration like I do here. You then just 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);
        });
        ...
    }
    

    Easy solution: 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

    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