Keycloakify
HomeGitHubStorybookAlternative to keycloak-js
v11 (Legacy)
  • Documentation
  • Release Notes & Upgrade Instructions
  • FAQ
v11 (Legacy)
  • 👨‍💻Quick start
  • 🧪Testing your Theme
    • In Storybook
    • In a Keycloak Docker Container
    • With Vite or Webpack in dev mode
  • 🔩Integrating Keycloakify in your Codebase
    • In your React Project
      • In your Vite Project
      • In your Webpack Project
    • As a Subproject of your Monorepo
      • Turborepo
      • Nx Integrated Monorepo
      • Package Manager Workspaces
  • 🎨Customization Strategies
    • CSS Level Customization
      • Basic example
      • Removing the default styles
      • Applying your own classes
      • Page specific styles
      • Using Tailwind
      • Using custom assets
        • .css, .sass or .less
        • CSS-in-JS
    • Component Level Customization
      • Using custom assets
  • 🖋️Custom Fonts
  • 🌎Internationalization and Translations
    • Base principles
    • Adding Support for Extra Languages
    • Previewing you Pages In Different Languages
    • Adding New Translation Messages Or Changing The Default Ones
  • 🎭Theme Variants
  • 📝Customizing the Register Page
  • 👤Account Theme
    • Single-Page
    • Multi-Page
  • 📄Terms and conditions
  • 🖇️Styling a Custom Page Not Included In Base Keycloak
  • 🔧Accessing the Server Environment Variables
  • 🎯Targetting Specific Keycloak Versions
  • 📧Email Customization
  • 🚛Passing URL Parameters to your Theme
  • 🤵Admin theme
  • 📥Importing the JAR of Your Theme Into Keycloak
  • 🔛Enabling your Theme in the Keycloak Admin Console
  • 🤓Taking ownership of the kcContext
  • 📖Configuration Options
    • --project
    • keycloakVersionTargets
    • environmentVariables
    • themeName
    • startKeycloakOptions
    • themeVersion
    • postBuild
    • XDG_CACHE_HOME
    • kcContextExclusionsFtl
    • keycloakifyBuildDirPath
    • groupId
    • artifactId
    • Webpack specific options
      • projectBuildDirPath
      • staticDirPathInProjectBuildDirPath
      • publicDirPath
  • FAQ & HELP
    • 🤝Comunity resources
    • ⬆️Migration Guides
      • ⬆️v10->v11
      • ⬆️v9 -> v10
      • ⬆️CRA -> Vite
      • ⬆️v8 -> v9
      • ⬆️v7 -> v8
      • ⬆️v6 -> v7
      • ⬆️v6.x -> v6.12
      • ⬆️v5 -> v6
    • 😞Can't identify the page to customize?
    • 🤔How it Works
    • 😖Some values you need are missing from in kcContext type definitions?
    • ❓Can I use it with Vue or Angular
      • Angular
    • ⚠️Limitations
    • 🛑Errors Keycloak in Logs
    • 🙋How do I add extra pages?
    • 🤓Can I use react-hooks-form?
    • 🚀Redirecting your users to the login/register pages
    • 💟Contributing
    • 🍪Google reCaptcha and End of third-party Cookies
    • 🔖Accessing the Realm Attributes
  • ⭐Sponsors
Powered by GitBook
On this page
  • Changes to the i18n system
  • Some components logic have been abstracted away
  • kcSanitize
  • keycloakify-resouces has been renamed keycloakify-dev-resources
  • Keys in keycloakVersionTargets have been updated

Was this helpful?

Edit on GitHub
  1. FAQ & HELP
  2. Migration Guides

v10->v11

Last updated 5 months ago

Was this helpful?

Don't worry, this is not like v9 to v10, this update is very easy. \

See release note:

Pay close attention. The syntax hylighting of gitbook for diffs is buggy. Don't look only at the colors. Look at the + and - symbols at the start of each line.

Changes to the i18n system

src/login/i18n.ts
-import { createUseI18n } from "keycloakify/login";
+import { i18nBuilder } from "keycloakify/login";
+import type { ThemeName } from "../kc.gen";


-export const { useI18n, ofTypeI18n } = createUseI18n({
+const { useI18n, ofTypeI18n } = i18nBuilder
+    .withThemeName<ThemeName>()
+    .withCustomTranslations({
        en: {
          backToLogin: "⏪ Back to <strong>Login page</strong>",
          myCustomKey: "My custom message",
        },
        fr: {
          backToLogin: "⏪ Retour à la <strong>page de Login</strong>",
          myCustomKey: "Mon message personalisé",
        },
     })
+    .build()
      
-export type I18n = typeof ofTypeI18n;
+type I18n = typeof ofTypeI18n;

+export { useI18n, type I18n };

If you have ejected the Template.tsx the language selector code has changed a little:

src/login/Template.tsx
-   const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
+   const { realm, auth, url, message, isAppInitiatedAction } = kcContext;
-   const { msg, msgStr, currentLanguageTag } = i18n;
+   const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;

    // ...
    
-   useEffect(() => {
-       const { currentLanguageTag } = locale ?? {};
-       if (currentLanguageTag === undefined) {
-           return;
-       }
-       const html = document.querySelector("html");
-       assert(html !== null);
-       html.lang = currentLanguageTag;
-   }, []);

    return (
        <div className={kcClsx("kcLoginClass")}>
            <div id="kc-header" className={kcClsx("kcHeaderClass")}>
                <div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
                    {msg("loginTitleHtml", realm.displayNameHtml)}
                </div>
            </div>

            <div className={kcClsx("kcFormCardClass")}>
                <header className={kcClsx("kcFormHeaderClass")}>
-                   {realm.internationalizationEnabled && (assert(locale !== undefined), locale.supported.length > 1) && (
+                   {enabledLanguages.length > 1 && (
                        <div className={kcClsx("kcLocaleMainClass")} id="kc-locale">
                            <div id="kc-locale-wrapper" className={kcClsx("kcLocaleWrapperClass")}>
                                <div id="kc-locale-dropdown" className={clsx("menu-button-links", kcClsx("kcLocaleDropDownClass"))}>
                                    <button
                                        tabIndex={1}
                                        id="kc-current-locale-link"
                                        aria-label={msgStr("languages")}
                                        aria-haspopup="true"
                                        aria-expanded="false"
                                        aria-controls="language-switch1"
                                    >
-                                        {labelBySupportedLanguageTag[currentLanguageTag]}
+                                        {currentLanguage.label}
                                    </button>
                                    <ul
                                        role="menu"
                                        tabIndex={-1}
                                        aria-labelledby="kc-current-locale-link"
                                        aria-activedescendant=""
                                        id="language-switch1"
                                        className={kcClsx("kcLocaleListClass")}
                                    >
-                                       {locale.supported.map(({ languageTag }, i) => (
-                                           <li key={languageTag} className={kcClsx("kcLocaleListItemClass")} role="none">
-                                               <a
-                                                   role="menuitem"
-                                                   id={`language-${i + 1}`}
-                                                   className={kcClsx("kcLocaleItemClass")}
-                                                   href={getChangeLocaleUrl(languageTag)}
-                                               >
-                                                   {labelBySupportedLanguageTag[languageTag]}
+                                       {enabledLanguages.map(({ languageTag, label, href }, i) => (
+                                           <li key={languageTag} className={kcClsx("kcLocaleListItemClass")} role="none">
+                                               <a role="menuitem" id={`language-${i + 1}`} className={kcClsx("kcLocaleItemClass")} href={href}>
+                                                   {label}
                                                </a>
                                            </li>
                                        ))}
                                    </ul>
                                </div>
                            </div>
                        </div>
                    )}

Some components logic have been abstracted away

This only applies if you have ejected the mentioned components.

src/login/Template.tsx
import { useEffect } from "react";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import type { TemplateProps } from "keycloakify/login/TemplateProps";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
-import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
-import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
+import { useInitialize } from "keycloakify/login/Template.useInitialize";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { I18n } from "./i18n";
import type { KcContext } from "./KcContext";

export default function Template(props: TemplateProps<KcContext, I18n>) {
    const {
        displayInfo = false,
        displayMessage = true,
        displayRequiredFields = false,
        headerNode,
        socialProvidersNode = null,
        infoNode = null,
        documentTitle,
        bodyClassName,
        kcContext,
        i18n,
        doUseDefaultCss,
        classes,
        children
    } = props;

    const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });

    const { msg, msgStr, getChangeLocaleUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;

    const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;

    useEffect(() => {
        document.title = documentTitle ?? msgStr("loginTitle", kcContext.realm.displayName);
    }, []);

    useSetClassName({
        qualifiedName: "html",
        className: kcClsx("kcHtmlClass")
    });

    useSetClassName({
        qualifiedName: "body",
        className: bodyClassName ?? kcClsx("kcBodyClass")
    });

-   const { areAllStyleSheetsLoaded } = useInsertLinkTags({
-       componentOrHookName: "Template",
-       hrefs: !doUseDefaultCss
-           ? []
-           : [
-                 `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
-                 `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
-                 `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`,
-                 `${url.resourcesCommonPath}/lib/pficon/pficon.css`,
-                 `${url.resourcesPath}/css/login.css`
-             ]
-   });

-   const { insertScriptTags } = useInsertScriptTags({
-       componentOrHookName: "Template",
-       scriptTags: [
-           {
-               type: "module",
-               src: `${url.resourcesPath}/js/menu-button-links.js`
-           },
-           ...(authenticationSession === undefined
-               ? []
-               : [
-                     {
-                         type: "module",
-                         textContent: [
-                             `import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`,
-                             ``,
-                             `checkCookiesAndSetTimer(`,
-                             `  "${authenticationSession.authSessionId}",`,
-                             `  "${authenticationSession.tabId}",`,
-                             `  "${url.ssoLoginInOtherTabsUrl}"`,
-                             `);`
-                         ].join("\n")
-                     } as const
-                 ]),
-           ...scripts.map(
-               script =>
-                   ({
-                       type: "text/javascript",
-                       src: script
-                   }) as const
-           )
-       ]
-   });

-   useEffect(() => {
-       if (areAllStyleSheetsLoaded) {
-           insertScriptTags();
-       }
-   }, [areAllStyleSheetsLoaded]);

-   if (!areAllStyleSheetsLoaded) {
-       return null;
-   }

+   const { isReadyToRender } = useInitialize({ kcContext, doUseDefaultCss });

+   if (!isReadyToRender) {
+       return null;
+   }

    return (
        <div className={kcClsx("kcLoginClass")}>

Similar changes have been made to the following pages:

kcSanitize

Keycloakify now implements a kcSanitize function analogous to the one used by the default Keycloak theme.

You can search/replace in your codebase for any dangerouslySetInnerHTML occurence and wrap the html string into the kcSanitize method:

+import { kcSanitize } from "keycloakify/lib/kcSanitize";

// ...

            <span
                className="kc-feedback-text"
                dangerouslySetInnerHTML={{
-                   __html: message.summary
+                   __html: kcSanitize(message.summary)
                }}
            />

keycloakify-resouces has been renamed keycloakify-dev-resources

This change is only required for Webpack users. Not Vite users.

package.json
 {
     "scripts": {
         "prestart": "keycloakify update-kc-gen && keycloakify copy-keycloak-resources-to-public",
         "start": "react-scripts start",
         "prestorybook": "npm run prestart",
         "storybook": "storybook dev -p 6006",
         "prebuild": "keycloakify update-kc-gen",
         "build": "react-scripts build",
-        "postbuild": "rimraf build/keycloakify-resources",
+        "postbuild": "rimraf build/keycloakify-dev-resources",
         "build-keycloak-theme": "npm run build && keycloakify build",
         "format": "prettier . --write"
         // ...
     },

Keys in keycloakVersionTargets have been updated

This change is only required for users of version 10.1.4 and below who use the keycloakVersionTargets configuration.

You can get help updating your keycloakVersionTargets configuration through .

⬆️
⬆️
import { useScript } from "keycloakify/login/pages/LoginPasskeysConditionalAuthenticate.useScript";
import { useScript } from "keycloakify/login/pages/LoginRecoveryAuthnCodeConfig.useScript";
import { useScript } from "keycloakify/login/pages/WebauthnAuthenticate.useScript";
import { useScript } from "keycloakify/login/pages/WebauthnRegister.useScript";
targeting-specific-keycloak-versions
Releases · keycloakify/keycloakifyGitHub
Logo