Keycloakify
Keycloakify is a framework designed to streamline the creation of custom Keycloak user interfaces using React. It acts as a build tool that compiles a React application into a Keycloak-compatible theme, abstracting away the complexities of FreeMarker (FTL) templating. The library is actively maintained, with the current stable version being 11.15.3, and exhibits a fairly rapid release cadence, often pushing several minor or patch updates within a short period. Keycloakify's primary differentiator is its ability to enable modern web development practices (like React) for Keycloak theming, supporting a broad range of Keycloak versions from 11 up to 26 and beyond. This allows developers to leverage familiar tooling and component-based architectures for login, registration, account management, and other Keycloak-provided pages, rather than directly interacting with FreeMarker templates.
Common errors
-
Error: Theme 'your-theme-name' not found
cause The generated JAR theme file was not correctly deployed to Keycloak or the theme name in Keycloak's admin console does not match the one configured in `package.json`.fixAfter `keycloakify build`, copy the generated `.jar` file from `dist_keycloak/` to the Keycloak server's `providers` directory (e.g., `/opt/keycloak/providers/` in Docker). Ensure the `themeName` in your `package.json`'s `keycloakify` configuration matches what you select in the Keycloak admin console. -
ReferenceError: kcContext is not defined
cause This error typically occurs during development or when trying to access `kcContext` outside the Keycloak theme's runtime environment (e.g., in your main application bundle or if the Keycloakify theme is not correctly initialized).fixEnsure `KcApp` is correctly conditionalized to only render your Keycloak theme components when `window.kcContext` is present. During Storybook development, `kcContext` is usually mocked. -
Build failed: No theme entry point found.
cause Keycloakify could not find the expected entry point for your theme, typically `src/keycloak-theme/KcApp.tsx` or similar, or the `keycloakify` configuration in `package.json` is incorrect.fixVerify that your theme entry file (e.g., `src/keycloak-theme/KcApp.tsx`) exists and is correctly exporting your main theme component. Check your `package.json` for the `keycloakify` configuration and ensure `themeName` and other paths are correct.
Warnings
- breaking Themes built with Keycloakify versions prior to Keycloak 26 are incompatible with Keycloak 26. This is due to significant internal changes in Keycloak, including marshalling format, User Profile SPI, and FreeMarker template updates.
- gotcha Keycloakify only supports out-of-the-box the most common user-facing Keycloak pages (login, registration, account). If a page you need to customize is not provided as a React component by Keycloakify, you might need to implement it yourself or 'eject' it.
- gotcha Directly modifying the `KcContext` type definitions or relying on implicit runtime properties can lead to issues, especially when Keycloak or Keycloakify updates.
- gotcha Older versions of Keycloakify could produce numerous FTL (FreeMarker Template Language) errors in Keycloak logs, especially with Keycloak 15+. While often ignorable, they could indicate performance issues.
Install
-
npm install keycloakify -
yarn add keycloakify -
pnpm add keycloakify
Imports
- KcContext
import { KcContext } from 'keycloakify';import type { KcContext } from 'keycloakify'; - createUseKcContext
import { useKcContext } from 'keycloakify';import { createUseKcContext } from 'keycloakify'; - useKcMessage
import { useKcMessage } from 'keycloakify';import { useKcMessage } from 'keycloakify/lib/i18n';
Quickstart
import { lazy, Suspense } from 'react';
import type { PageProps } from 'keycloakify/lib/KcProps';
import { useKcMessage } from 'keycloakify/lib/i18n';
import { createUseKcContext, get } from 'keycloakify';
import type { KcContext } from 'keycloakify';
// 1. Define your custom KcContext extension (optional)
// This example extends the context for a custom 'my-custom-page.ftl'
export type KcContextExtension = {
customData: string;
};
export type KcContextExtended = KcContext & KcContextExtension;
// 2. Create your custom useKcContext hook
export const { useKcContext } = createUseKcContext<KcContextExtension>();
// 3. Main application component that renders Keycloak pages
export default function KcApp(props: PageProps<KcContextExtended>) {
const { kcContext } = props;
const { msg } = useKcMessage();
// Dynamically set page title
if (kcContext) {
document.title = msg("doLogIn"); // Example: set based on a common message key
}
// Lazy load page components based on Keycloak's pageId
const PageComponent = kcContext ? lazy(() => {
switch (kcContext.pageId) {
case 'login.ftl': return import('./pages/KcLogin');
case 'register.ftl': return import('./pages/KcRegister');
// Add more cases for other Keycloak pages you want to customize
// For custom pages (e.g., 'my-custom-page.ftl'), ensure they are handled
case 'my-custom-page.ftl': return import('./pages/MyCustomPage');
default: return import('./pages/KcDefaultPage'); // Fallback for unhandled pages
}
}) : null;
return (
<Suspense fallback={<div>Loading Keycloak page...</div>}>
{kcContext && PageComponent ? <PageComponent {...{ kcContext }} /> : <div>No Keycloak context available.</div>}
</Suspense>
);
}
// src/keycloak-theme/pages/KcLogin.tsx (Simplified example)
// import React from 'react';
// import type { PageProps } from 'keycloakify/lib/KcProps';
// import type { KcContextExtended } from '../KcApp'; // Use your extended context
// import { useKcMessage } from 'keycloakify/lib/i18n';
// export default function KcLogin(props: PageProps<KcContextExtended>) {
// const { kcContext } = props;
// const { msg } = useKcMessage();
// return (
// <div>
// <h1>{msg('loginTitle')}</h1>
// <p>Welcome to the custom login page for {kcContext.realm.displayName}!</p>
// <form action={kcContext.url.loginAction} method='post'>
// <input type='text' id='username' name='username' placeholder={msg('username')} />
// <input type='password' id='password' name='password' placeholder={msg('password')} />
// <button type='submit'>{msg('doLogIn')}</button>
// </form>
// </div>
// );
// }
// package.json (add this script)
/*
{
"name": "my-keycloak-theme",
"version": "1.0.0",
"scripts": {
"build-keycloak-theme": "keycloakify build"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"keycloakify": "^11.0.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0"
}
}
*/