I'm working on the following setup:
As I'm a backend dev and have little to no experience with React, I wanted to let Spring handle all the authentication and authorization against Keycloak. Thus, I've configured:
spring:
security:
oauth2:
client:
registration:
keycloak:
clientId: <my id>
clientSecret: <my secret>
scope: openid
provider:
keycloak:
issuerUri: <my issuer uri>
user-name-attribute: preferred_username
and set the oauth2 config:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(Customizer.withDefaults())
.cors(Customizer.withDefaults())
.exceptionHandling(customizer -> customizer.accessDeniedHandler(customAccessDeniedHandler()))
.authorizeHttpRequests((customizer) -> customizer.requestMatchers(
"/oauth2/login",
"/oauth2/logout/success",
"/oauth2/error",
).permitAll()
.anyRequest().authenticated()
).oauth2Login(configurer -> {
configurer.userInfoEndpoint(customizer -> customizer.userAuthoritiesMapper(customGrantedAuthoritiesMapper()));
configurer.loginPage("/oauth2/login");
configurer.defaultSuccessUrl("/oauth2/success");
configurer.failureUrl("/oauth2/error");
});
}
When I access some protected endpoint (let's say /api/test) from React, Spring sends a redirect (per Location-header) to
http://localhost:8080/oauth2/login
which then redirects to
http://localhost:8080/oauth2/authorization/keycloak
which redirects to Keycloak on
http://localhost:8090/realms/my-realm/protocol/openid-connect/auth?response_type=code&client_id=my-client-id&scope=openid&state=my-state&redirect_uri=http://localhost:8086/login/oauth2/code/keycloak&nonce=my-nonce*
When calling it directly from the browser, it works fine and I see the login form, can login and access my protected resources afterwards with the right roles set into the principal. But as soon as the request's origin is my frontend webapp, the last redirect to Keycloak fails because of missing CORS header Access-Control-Allow-Origin.
Of course I followed the manual and set the valid resource urls as well as web origins inside Keycloak's client settings. But nothing worked, I tried to
Nothing worked, I tried multiple solutions like those on StackOverflow, Discourse (1), Discourse (2) and multiple others, but to no avail. Unfortunately Keycloak changed a bit in the past (of course), so most articles are outdated. But finally I've stumbled upon this resource saying in point 7 that this CORS error indicates "wrong backend/API implementation or used flow".
So long story short: has anyone managed to get it running like described? Should I change my architecture to let React handle the authentication with Keycloak, sending JWT token to Spring instead?
Thank you very much for your opinions and insights.
Cross-origin issues happen only with XHR requests. But there is no need to perform authorization code flow (and RP-Initiated Logout) with the app internal HTTP client. If setting windows.location
instead of using the internal HTTP client, redirections will be plain requests too and you won't have cross-origin issues (because requests are not cross-origin any-more).
Two options when needing to log a user in:
windows.location=http://localhost:8080/oauth2/login
(but if you configure more than one registration
with authorization_code
, this is probably unacceptable).http://localhost:8080/oauth2/authorization/keycloak
and configure your Spring app to answer in the 2xx
range. The Javascript can then observe the response and follow the URI in the Location
header. I do so in this Baeldung article I wrote for details.