2

keycloak-connect, which is the recommended NodeJS adapter from the Keycloak docs, does not take into account the X-Forwarded-Proto header when the protected application is sitting behind an Apache reverse proxy.

Indeed the redirectUri is built this way :

let host = request.hostname;
let headerHost = request.headers.host.split(':');
let port = headerHost[1] || '';
let protocol = request.protocol;*
let hasQuery = ~(request.originalUrl || request.url).indexOf('?');

let redirectUrl = protocol + '://' + host + (port === '' ? '' : ':' + port) + (request.originalUrl || request.url) + (hasQuery ? '&' : '?') + 'auth_callback=1';

request.protocol always is "http" due to the reverse proxy, thus the redirectUri does not have the expected protocol (HTTPS).

If this is intentional and not a bug, is using HTTP in the redirectUri a security flaw even if the client is redirect to HTTPS ? Couldn't the token have been exposed in the meantime ?

ojathelonius
  • 685
  • 8
  • 26

1 Answers1

3

It turns out to be a configuration issue on the protected application side, which also concern other Keycloak adapters such as the Java Servlet Filter adapter or the Spring adapter.

By default, any protected application will ignore the X-Forwarded-Proto header.

NodeJS

The web framework used (such as Express) needs to be configured to take proxy headers into account.

For Express, the documentation states that the application should trust the proxy, e.g. :

app.set('trust proxy', 'loopback');

Java Adapters

For Spring, there is very little configuration to do as per the docs, in application.properties :

server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto 

For the Java Servlet Filter, there are several solutions :

A Tomcat valve can be added to server.xml :

<Valve className="org.apache.catalina.valves.RemoteIpValve" 
           internalProxies="127.0.0.1" 
           remoteIpHeader="x-forwarded-for" 
           proxiesHeader="x-forwarded-by" 
           protocolHeader="x-forwarded-proto" /> 

It is also possible to implement a new Servlet Filter before Keycloak's Filter Adapter, registered in web.xml, just like in this example.

ojathelonius
  • 685
  • 8
  • 26
  • 1
    This was exactly what I needed for a nodejs app with some minor modifications as detailed in the documentation you linked. Basically, since my app was deploying in a kubernetes cluster, I could not use loopback (since the reverse proxy was another node in the cluster). Instead, I had to use uniquelocal, since that includes 10.0.0.0/8, which covers all the IP addresses of the pods in my cluster. – redsoxfantom Feb 10 '23 at 20:24