June
June

Reputation: 436

CORS Error with Keycloak despite correct Web Origins and setup in Docker environment

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.

Environment Setup

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"
  ]

Keycloak Settings

  1. Web Origins:
    • http://localhost:3000
    • http://localhost:8080
  2. Valid Redirect URIs:
    • http://localhost:3000/*
    • http://localhost:8080/*
  3. Valid Post Logout Redirect URIs:
    • http://localhost:3000

Problem Description

When a user accesses the frontend (http://localhost:3000) and attempts to authenticate via Keycloak, the following flow occurs:

  1. The client sends a request to the backend (http://localhost:8080/success-page).
  2. If not authenticated, the backend redirects the user to the Keycloak login page at http://localhost:8090.
  3. The browser then encounters a CORS error while trying to access Keycloak's login page or its static resources (e.g., CSS, JS).

Here's what I've verified so far:

What I've Tried

  1. Keycloak Configuration:
    • Ensured that Web Origins and Valid Redirect URIs are correctly configured.
    • Added KC_SPI_HOSTNAME_DEFAULT_FORWARDED=true to the Keycloak Docker environment.
  2. Frontend Configuration:
    • Verified that the frontend uses the correct Keycloak client.
  3. Spring Gateway:
    • Implemented a CorsConfigurationSource to allow CORS for all paths:
@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;
}
  1. Keycloak HTTP Configuration:
    • Tested Keycloak with KC_RESPONSE_HEADERS to explicitly add Access-Control-Allow-Origin, but no change.
  2. Keycloak Theme:
    • Explored modifying the Keycloak login page's Freemarker template (login.ftl) to add meta tags for CORS headers but prefer not to rely on this hack.

Constraints

Questions

  1. Why is Keycloak not responding with Access-Control-Allow-Origin in the CORS headers, even though Web Origins is correctly set?
  2. Is there a specific Keycloak configuration or Docker setting I'm missing to ensure proper CORS handling?
  3. Are there any known issues with CORS in Keycloak v26.0.7 or a better way to debug/prevent this issue?

Additional Context

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

  1. :3000/test → Button click (request to the backend :8080/success-page)

  2. (302 redirect) → :8080/oauth2/authorization/keycloak

  3. (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

Answers (1)

ch4mp
ch4mp

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):

  • on the Gateway, permitAll() all requests routed with the TokenRelay filter and have the downstream resource server answer with 401 when authorization is required
  • configure Spring Security to answer 401 to anonymous requests on the routes with the TokenRelay filter

Second, 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

Related Questions