This article describes how you can use Keycloak behind an API-gateway that considers CORS requests, which by default does not work. It presents a tutorial for a work-around that can be used until a proper fix for the root-cause of the problem is provided by Keycloak.
The Problem
The default setup will cause an HTTP 403 Forbidden response from the API-gateway during the authenticate-step on the Keycloak login page because the browser sends the HTTP request-header ‘origin: null‘, which is identified by the API-gateway as a CORS-request, and denied because ‘null‘ is not an allowed origin.
The root-cause for this behavior is that Keycloak always sends the HTTP response-header ‘Referrer-Policy: no-referrer‘. This instructs the browser to omit the Referer HTTP request-header, and send the HTTP request-header ‘origin: null‘ for subsequent requests to Keycloak.
Our solution is to rewrite the HTTP response-header from Keycloak in the API-gateway, so that instead of ‘Referrer-Policy: no-referrer‘ the browser will receive ‘Referrer-Policy: same-origin‘.
Background
An API-gateway, such as Spring Cloud Gateway, is typically used in a microservice architecture to expose multiple services at a single endpoint.
It is not uncommon to expose an IAM-solution, such as Keycloak, via an API-gateway.
Services may require CORS support for some endpoints, which is typically managed at the API-gateway level.
Keycloak
According to https://www.keycloak.org/about “Keycloak is an open-source Identity and Access Management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code.”
Spring Cloud Gateway
According to https://spring.io/projects/spring-cloud-gateway the Spring Cloud Gateway “ provides a library for building an API Gateway on top of Spring WebFlux. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross-cutting concerns to them such as security, monitoring/metrics, and resiliency.”
CORS
According to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS “Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.”
How to Reproduce the Problem
In order to reproduce the problem, you need the following setup:
- Keycloak version 12 or higher (version 12 introduced the Referrer-Policy response header, although there is a bug report that states it also occurs with version 11.0.2)
- API-gateway such as Spring Cloud Gateway with CORS enabled
The following steps will then reproduce the problem:
- Navigate to the Keycloak login page with a browser (we used both Firefox and Chrome), e.g. http://localhost:8080/auth/admin/ for a local setup.
- Observe the HTTP response-header ‘Referrer-Policy: no-referrer‘ in the browser developer-tools network tab.
- Provide valid login credentials, and click the Sign In button.
- Observe the HTTP response 403 Forbidden and the empty page in the browser.
- Observe the HTTP request header ‘origin: null’.
If you cannot reproduce the problem, be sure that:
- The browser communicates with the API-gateway, and not with Keycloak directly.
- CORS is enabled in the API-gateway using non-wildcard ‘Access-Control-Allow-Origin‘, e.g. using configuration properties such as
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOrigins="https://some.domain.org"
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=GET
The Solution
The problem can be fixed (or rather, worked-around) by rewriting the HTTP response-header from Keycloak in the API-gateway, e.g. so that instead of ‘Referrer-Policy: no-referrer‘ the browser receives ‘Referrer-Policy: same-origin‘.
Spring Cloud Gateway provides a convenient RewriteResponseHeaderGatewayFilterFactory for this, which we set up as follows:
@Bean
public RouteLocator theRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth", r ->
r.path("/auth/**")
.filters(f -> f.rewriteResponseHeader("Referrer-Policy", "no-referrer", "same-origin"))
.uri("https://keycloak"))
.build();
}
Then you can repeat the steps to observe that the fix works
- Navigate to the Keycloak login page with a browser, e.g. http://localhost:8080/auth/admin/ for a local setup
- Observe the HTTP response-header ‘Referrer-Policy: same-origin‘ in the browser developer-tools network tab
- Provide valid login credentials, and click the Sign In button.
- Observe the HTTP response 302 Found and the target page in the browser.
- Observe the HTTP request header ‘origin: http://localhost:8080‘.
Questions and Considerations
Why Is the API-gateway Rejecting the HTTP Request?
The browser sends the HTTP request-header ‘origin: null‘ when the ‘Referrer-Policy‘ is ‘no-referrer‘.
Whenever the ‘origin‘ header is present in the HTTP request, the API-gateway considers it a CORS request. A CORS request causes the API-gateway to validate if the origin is in the list of allowed origins. For ‘null‘ this is typically not the case (as it’s not recommended), leading it to reject the request with HTTP 403 Forbidden.
Why Don’t You Just Allow null as CORS Origin?
Allowing ‘null‘ as ‘Access-Control-Allow-Origin‘ is not recommended, as it introduces multiple security problems. See https://w3c.github.io/webappsec-cors-for-developers/#avoid-returning-access-control-allow-origin-null for more details.
Why Don’t You Just Allow All Origins / Methods for CORS?
While allowing all origins/methods for CORS would prevent the problem, it would also introduce significant security issues. CORS settings should always be as restrictive as possible, especially when sensitive data (such as credentials) is involved. See https://stackoverflow.com/a/56457665 for details.
Shouldn’t Keycloak Fix This Issue?
We would recommend Keycloak to make the ‘Referrer-Policy‘ value configurable, just as they allow for certain other headers.
The following Keycloak bug-report was created (not by us) in October 2020 for this issue, suggesting exactly this: https://issues.redhat.com/browse/KEYCLOAK-16032
Somehow the Keycloak devs got confused in the bug thread and closed it as ‘explained’ by stating that ‘null‘ is a valid value for ‘origin‘ (which is not the issue), rather than actually fixing the problem (e.g. by making the ‘Referrer-Policy‘ customizable, similar to other HTTP response header values).
Doesn’t This Work-Around Reduce Overall Security?
It is true that ‘no-referrer‘ is a stricter ‘Referrer-Policy‘ compared to e.g. ‘same-origin‘, as it causes the browser to send fewer details to the API-gateway in the ‘Referer‘ and ‘origin‘ headers.
However, in combination with a CORS-enabled API-gateway, using ‘no-referrer‘ totally breaks Keycloak from a system-level perspective, rendering it completely useless. So using this setting provides the same level of security and functionality as shutting down Keycloak.
In order to make this work with a CORS-enabled API-gateway, the browser must send some more details, specifically a proper value for the ‘origin‘ header for requests with the same origin. This can be achieved by using a different ‘Referrer-Policy‘, such as ‘same-origin‘.
For us this is an acceptable (very limited) reduction in security to at least have the expected functionality; in addition, these details only affect the same origin, which is fully under our control.
Can I Use Other Referrer-Policy Values?
You can choose a referrer-policy that works for your setup, there is no requirement to use ‘same-origin‘. For example, the typical browser-default ‘strict-origin-when-cross-origin‘ works fine as well.
Further Details
- Referrer-Policy response header
- Keycloak always sending ‘Referrer-Policy: no-referrer‘ Part 1 + Part 2
- Bug report for Keycloak that was not fixed but only explained
- Why origin ‘null‘ should not be an allowed origin for CORS
- Spring Cloud Gateway RewriteResponseHeaderGatewayFilterFactory
- Spring CorsUtils to determine if an HTTP request is CORS or not
- General information on CORS
- Chromium devs confirming the no-referrer policy behavior