Mitchell Hillman

Launching React from a Keycloak theme

Keycloak is highly themable using freemarker templates. But I need more than that to support a shared library of UI components used in the rest of my application. The solution is to use a window.CONFIG object to provide theme values to a React application which is launched from registrationLayout template. The React application can then use the shared components at build time.

Template files

A react-template.ftl layout is created in the keycloak theme. This is root level HTML for the theme and has markup for mounting the React application.

react-template.ftl

<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true>
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <title>App Name</title>
    <meta charset="utf-8"/>
    <link rel="icon" href="${url.resourcesPath}/img/favicon.ico"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link href="${url.resourcesPath}/css/reset.css" rel="stylesheet">
    <script defer="defer" src="${url.resourcesPath}/js/main.a9c8f6d7.js"></script>
    <#nested "form">
  </head>
  <body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  </body>
  </html>
</#macro>

Note the <#nested "form"> inside the <head> tag

Each themed page in keycloak is a simple template which only defines its parent layout and sets the theme values to an object created in global scope.

login.ftl

<#import "react-template.ftl" as layout>
<@layout.registrationLayout displayInfo=true; section>
  <#outputformat "HTML">
    <script>
      window.CONFIG = {
        resourceUrl: "${url.resourcesPath}",
        loginAction: "${url.loginAction?no_esc}",
        forgetPasswordUrl: "${url.loginResetCredentialsUrl}",
        <#if !usernameHidden??>
          usernameHidden: false,
        <#else>
          usernameHidden: true,
        </#if>
        <#if message?has_content>
          message: {
            summary: '${message.summary}',
            type: '${message.type}',
          },
        </#if>
        env: "theme"
      };
    </script>
  </#outputformat>
</@layout.registrationLayout>

Deployment

package.json:

"build": "react-scripts build && bash postbuild.sh",

This example assumes Create React App as a starting point for the React app, hence the react-scripts for the build. The important point to note is that the custom postbuild.sh is called after the react app is built.

postbuild.sh:

#!/usr/bin/env bash

# copy app resources to theme folder
pathToResources="./keycloak/themes/lsm/login/resources"
rm -rf $pathToResources
cp -r ./build/static $pathToResources

# get hash from the built index.html
build="$(cat build/index.html)"
regex="(.*main.)(.*)(.js.*)"
if [[ $build =~ $regex ]]; then
  hash=${BASH_REMATCH[2]}
fi

# replace hash in react-template.ftl
template="$(cat ./keycloak/themes/lsm/login/react-template.ftl)"
if [[ $template =~ $regex ]]; then
  new="${BASH_REMATCH[1]}$hash${BASH_REMATCH[3]}"
fi
echo "$new" > "./keycloak/themes/lsm/login/react-template.ftl"