Keycloak, OpenShift, and Emails: A Tale of Links With Wrong Base URLs
By: Arturo Martínez
Tags: DevOps, Keycloak, Openshift, Programming, Kubernetes

Links in Keycloak emails that are sent using its admin API can have some funky URLs if the actor who triggers the sending is a neighboring service and our play takes place inside OpenShift or behind a reverse proxy. Here’s how to give our story a happy ending.

Consider the following ecosystem, in which we have Keycloak and a Node.js worker service deployed and running on OpenShift:

Let’s assume that, for whatever reason, the Node.js service runs a poor man’s cron job that triggers the sending of one of the required actions emails to a certain subset of users; it could be any of the available required actions, but for this example we will use the “update password” one. This Node.js service will be making a call to Keycloak’s admin REST API — with or without the help of the keycloak-admin module — . The endpoint we are hitting will trigger the sending of an email with a link that the user has to click in order to perform one or more required actions (e.g. update your password because it’s getting old).

Because both the Node.js service and Keycloak are running on OpenShift, a Keycloak with an out-of-the-box configuration will send an email that contains a link to update the user’s password and will be pointing to a faulty URL (e.g. http://keycloak:8080 ), which is obviously not accessible for the user.

Before we dive into the solution of our problem, let’s try to have a better understanding of what’s happening behind the scenes.

Understanding how Keycloak builds email links

If we take a look at some of Keycloak’s source code, we can come across something like this:

The builder in line 2 is created based on the context of the current session, originating in the incoming request from our Node.js service to Keycloak’s admin API. This request fired by our Node.js service is pointing to the internal Keycloak address in OpenShift — for example, http://keycloak:8080 . The link rendered in the resulting email will be generated with this base URL, which will lead to nowhere once the user clicks it.

We need to tell Keycloak what the right base URL should be. If you’ve dug deep in Keycloak’s FreeMarker templates…

<html>

<body>

${kcSanitize(msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc}

</body>

</html>

…you’ve probably found out that there’s no elegant way of change the base URL of the link.

Luckily, there’s a proper way to tell Keycloak what base URL to use.

Setting up the frontend URL in Keycloak

The concept of a “frontend URL” was explicitly enabled in Keycloak version 8.0.0. This configuration property is used to set a fixed base URL for frontend requests; by default, its value will be derived from the (incoming) request. For such an ecosystem as the one described at the beginning of this article, that won’t be a good idea. It’s also good to consider that the official Keycloak documentation encourages setting a frontend URL in production environments.

There are several ways to set a frontend URL:

If you are using a Keycloak as a Docker image, you can globally set the frontend URL using the environment variable KEYCLOAK_FRONTEND_URL . Otherwise, you can add the following to the startup: -Dkeycloak.frontendUrl=https://my.keycloak.instance.com/auth . It can also be added to Keycloak’s standalone.xml configuration. If you are using the jboss-cli tool, the instruction you need to issue is /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl,value=”https://my.keycloak.instance.com/auth") . Finally, you can override it for individual realms by setting it in the admin console:

Keycloak’s admin console, where the frontend URL can be overridden per realm.

If you want to add it in the standalone.xml , the end result could look something like this:

Client side configuration

If by any chance your ecosystem includes any Node.js service that interacts with Keycloak on a more client side way, and it uses the module keycloak-connect , then you have one more thing to configure!