Reputation: 436
I'm experiencing a persistent CORS issue when integrating Keycloak (v26.0.7) with my application running in a Docker environment. Despite having configured Web Origins and Redirect URIs correctly in Keycloak, I'm still facing CORS errors in the browser.
saip-keycloak:
image: quay.io/keycloak/keycloak:26.0.7
container_name: saip-keycloak
ports:
- "8090:8080"
volumes:
- ./keycloak/dev/seed/realm-import.json:/opt/keycloak/data/import/realm-import.json
- ./keycloak/dev/custom-theme:/opt/keycloak/themes/custom-theme
environment:
- KC_DB=mariadb
- KC_DB_URL=jdbc:mariadb://saip-mariadb-auth:3306/saip_auth_db
- KC_DB_USERNAME=auth_user1
- KC_DB_PASSWORD=auth_user1
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
- JAVA_OPTS_APPEND=-Dkeycloak.logging.level=DEBUG
- KC_SPI_HOSTNAME_DEFAULT_FORWARDED=true
command: [
"start-dev",
"--import-realm",
"--log-level=DEBUG",
"--hostname=localhost"
]
When a user accesses the frontend (http://localhost:3000) and attempts to authenticate via Keycloak, the following flow occurs:
Here's what I've verified so far:
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:3000");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Here's a snippet of the browser error:
Access to fetch at 'http://localhost:8090/realms/testrealm/protocol/openid-connect/auth' from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Note
In my case, it is the backend server that acts as the Client in the OAuth 2.0 flow, not the frontend server.
This means that the backend server, not the frontend server, is the entity that makes authentication requests to the Keycloak authentication server.
The user only performs the authentication when requested to do so.
Flow
:3000/test → Button click (request to the backend :8080/success-page)
(302 redirect) → :8080/oauth2/authorization/keycloak
(302 redirect) → :8090/realms/testrealm/protocol/openid-connect/auth/...
CORS Error Description
• The CORS error does not occur when redirecting from the frontend server to the Spring Boot backend server (:8080).
• However, the problem begins when redirecting from the backend server (:8080) to the Keycloak authentication server (:8090).
• During this step, the Origin header becomes null.
• Keycloak detects the null origin and, as a result, does not include CORS-related headers in its response.
• When the browser receives the response from Keycloak without the required headers, it triggers a CORS error.
Upvotes: 0
Views: 100
Reputation: 12669
As mentioned by @phil in his 1st comment, the redirection to the authorization server (Keycloak) should not be a cross-origin request, but a plain navigation.
First, a "smart" frontend consuming a REST API should answer 401 Unauthorized
and not 302 redirect to login
. For that, two options (the 1st is cleaner, especially if some resource servers allow anonymous access to some resources):
permitAll()
all requests routed with the TokenRelay
filter and have the downstream resource server answer with 401
when authorization is required401
to anonymous requests on the routes with the TokenRelay
filterSecond, theoretically, the frontend should redirect the user agent to the Gateway authorization initiation endpoint (by default /oauth2/authorization/{registration-id}
in a Spring application with oauth2Login
) with a plain navigation: set window.location.href
, not use its framework HTTP client like Axios. If it doesn't, the Gateway should be configured to set a status in the 2xx
range for the response with the location of the authorization server's authorization endpoint, so that the frontend code can read this answer and follow to that location by setting the window.location.href
. This is what I did in this Baeldung article.
Upvotes: 1