- Published on
How To Fix Keycloak Oauth2 OIDC Logout With Spring Cloud Gateway
- Authors
- Name
- Amrut Prabhu
- @amrutprabhu42
This is the third part of the sequence of articles where we integrate Keycloak with Spring Cloud Gateway using the OIDC Oauth2 mechanism.
You can go through the previous articles here:
- Spring Cloud Gateway Keycloak OAuth2 OIDC Integration
- Spring Cloud Gateway — Resource Server with Keycloak RBAC
What is the exact problem?
In my last articles, we communicated with the Spring Cloud Gateway application which is protected by the Keycloack Oauth2 OIDC authentication mechanism.
It all works like a charm until we reach the part of the logout.
When we try to logout using the /logout
path, the user just logs out from the current session in the Spring API Gateway application, but when we try to access the protected resource again, it logs back in as the user did not log out of the Keycloak session.
So here is a quick article on how to fix this issue.
Fixing the Logout Issue with Keycloak
Firstly, we need to add a logout handler to the API gateway's security settings.
For this, let's create a logout handler bean as below.
@Bean
public ServerLogoutSuccessHandler keycloakLogoutSuccessHandler(ReactiveClientRegistrationRepository repository) {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(repository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}/logout.html");
return oidcLogoutSuccessHandler;
}
Here I create a Client Initiated Server Logout handler. This initiates user logout on the authorization server i.e. Keycloak in our case.
Now to get the list of registered authorization servers, it needs the Client Registry Repository. This is where all the clients that we registered in the properties file as below as stored.
security:
oauth2:
client:
provider:
my-keycloak-provider:
issuer-uri: http://localhost:8080/realms/My-Realm registration:
keycloak-spring-gateway-client:
provider: my-keycloak-provider
scope: openid
client-id: spring-gateway-client
client-secret: 3RhEF8pqKTANrQ6BhfxaYVmcjTXsDK0u
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/keycloak"
In addition to this, we need to add the scope as openid
while registering the client as shown in the properties file above. This makes spring create a principal of typeOidcUser
when the user logs in.
Lastly, we have to set the logout handler in the security config as below.
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ServerLogoutSuccessHandler handler) {
http
.authorizeExchange()
.pathMatchers("/actuator/**", "/","/logout.html")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login() // to redirect to oauth2 login page. .and()
.logout()
.logoutSuccessHandler(handler);
return http.build();
}
Understanding this a little deeper
To understand this more deeper, Let’s have a look at the function onLogoutSuccess
inside the OidcClientInitiatedServerLogoutSuccessHandler
@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
return Mono.just(authentication)
.filter(OAuth2AuthenticationToken.class::isInstance)
.filter((token) -> authentication.getPrincipal() instanceof OidcUser)
.map(OAuth2AuthenticationToken.class::cast)
.map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId)
.flatMap(this.clientRegistrationRepository::findByRegistrationId)
.flatMap((clientRegistration) -> {
URI endSessionEndpoint = endSessionEndpoint(clientRegistration);
if (endSessionEndpoint == null) {
return Mono.empty();
}
String idToken = idToken(authentication);
String postLogoutRedirectUri = postLogoutRedirectUri(exchange.getExchange().getRequest());
return Mono.just(endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri));
})
....
In this function, you see that when the user logs out on the API gateway, on its success the token from the authenticated user is first checked if it was an oidcuser
. It then finds the registered client from the client registration repository and then calls the end session endpoint of the Keycloak server.
Conclusion
With this, we saw how we could fix the Keycloak logout issue with a simple fix.
You can refer to the entire code on my GitHub repo here.
I keep exploring and learning new things. If you want to know the latest trends and improve your software development skills, then subscribe to my newsletter below and also follow me on Twitter.
Enjoy!!