A PC in which a stick with a key is inserted, representing Keycloak

How to Use Keycloak with a CORS-enabled API-gateway

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

Julian Stücker, Solution Architect, and Thijs Reus, Co-Founder of trimplement, solve a Keycloak 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/aboutKeycloak 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/CORSCross-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:

  1. 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.
  2. Observe the HTTP response-header ‘Referrer-Policy: no-referrer‘ in the browser developer-tools network tab.
  1. Provide valid login credentials, and click the Sign In button.
  2. Observe the HTTP response 403 Forbidden and the empty page in the browser.
  3. 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

  1. Navigate to the Keycloak login page with a browser, e.g. http://localhost:8080/auth/admin/ for a local setup
  2. Observe the HTTP response-header ‘Referrer-Policy: same-origin‘ in the browser developer-tools network tab
  1. Provide valid login credentials, and click the Sign In button.
  2. Observe the HTTP response 302 Found and the target page in the browser.
  3. 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

Thijs Reus

Thijs Reus is one of the co-founders and managing directors of trimplement. He is a full-stack software engineer, with a Master’s degree in Computer Science. Thijs looks back on a successful professional career in consulting and financial software before he launched trimplement in 2010. With precision, Thijs meets the most intricate challenges in transaction processing, just to run a marathon the next day – for relaxation.

Leave a Reply

Your email address will not be published. Required fields are marked *