Search code examples
spring-securityfederated-identityspring-authorization-server

Logout in Spring Authorization server (acting as Identity broker) with Federated Identity Provider


I'm new to using Spring Boot and Spring security; I'm trying to build an Identity broker with Spring security Oauth2 components. The broker handles all the authorization, token and logout requests from my SPA(React) clients. It then delegates authentication to an external Identity Provider (Keycloak) which supports RP initiated logout.

I'm following the Federated Identity Sample provided in this issue.

The login works fine without any issues. As for logout, only the local session within the Identity broker is cleared. There is no request firing to the Identity provider's end_session_endpoint.

I did some reading and found the following code in the Spring security docs.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .oauth2Login(withDefaults())
            .logout(logout -> logout
                .logoutSuccessHandler(oidcLogoutSuccessHandler())
            );
        return http.build();
    }

    private LogoutSuccessHandler oidcLogoutSuccessHandler() {
        OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
                new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);

        // Sets the location that the End-User's User Agent will be redirected to
        // after the logout has been performed at the Provider
        oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");

        return oidcLogoutSuccessHandler;
    }
}

I configured the LogoutSuccessHandler to the Security filter chain. However, it is not being invoked when logout request is issued to the end_session_endpoint of my Identity broker /connect/logout from my React SPA. The parameters to the request are id_token_hint and post_logout_redirect_uri which is a Uri in my React app registered with my Identity broker (Authorization Server).

After some debugging, I noticed that the OidcLogoutEndpointFilter configured by default, handles the logout request at /connect/logout and checks for a post_logout_redirect_uri in the parameters and redirects to it.

How can I implement the RP initiated logout through my Identity broker in this scenario? The expected behavior is when I logout from my React app, the Identity broker issues logout request to the Federated Identity Provider, clears the local session and redirects back to my app.

I have found several questions and issues around this topic, but none that fits this specific case. Please point out to me if this has already been answered and I failed to find it. Thanks!


Solution

  • I'm posting a solution that worked well for me.

    After some reading, I found from the spring docs, and oidcLogoutEndpointFilter that the logoutResponseHandler is configurable. It is basically an implementation of the AuthenticationSuccessHandler interface.

    In my custom implementation of this interface, first, clearing out the local session associated with the requesting client by invoking the SecurityContextLogoutHandler. Then, constructing a redirect url using the end_session_endpoint of the external IdP, id_token issued by the external IdP, and the post_logout_redirect_uri from the original logout request and sending a redirect response back to the browser (https://external-idp.com/connect/logout?id_token_hint=''&post_logout_redirect_uri=''). Then, I configured this implementation as the logoutResponseHandler to the oidc.logoutEndpoint in the SecurityFilterChain for authorization server.

    This way, the user's session in the Identity broker is cleared as usual, and the session in the external IdP is also cleared by sending a request to its end_session_endpoint from the user's browser.