Всем привет! Меня зовут Артем, я фронтенд разработчик в Банки.ру. Недавно перед нашей командой встала задача внедрить SSO-аутентификацию через Keycloak для клиентских сервисов. Помимо базовой настройки, важно было полностью переработать стандартные формы входа, чтобы они соответствовали корпоративному стилю и UX-требованиям.

Эта статья будет полезна фронтенд-разработчикам, которые хотят кастомизировать интерфейс Keycloak, не жертвуя современным стэком, скоростью разработки и здравым рассудком. А также командам, которые находятся на этапе выбора решения для авторизации из имеющихся на рынке. Мы разберём практические шаги внедрения, покажем код и честно расскажем о подводных камнях, с которыми столкнулись сами.

О чем поговорим в статье:

  1. Проблема: ограничения кастомизации фронтенда для Keycloak

  2. Почему мы выбрали Keycloakify

  3. Процесс внедрения Keycloakify

  4. Проблемы, с которыми мы столкнулись, и как их решали

  5. Вместо вывода: стоит ли выбирать Keycloakify?

Итак, с какими проблемами мы столкнулись:

  • Ограничения стандартных тем Keycloak. Темы сложно кастомизировать без дополнительных инструментов, а дизайн "из коробки" выглядел устаревшим.

  • Бизнес-требования к формам. Были нужны нестандартные сценарии (например, согласие с политиками при регистрации или двухфакторная аутентификация с кастомным UI).

  • Проблемы CI/CD. Ручное копирование тем в Keycloak замедляло процесс разработки и обновлений.

Решение, которое мы нашли: Keycloakify – библиотека, которая позволяет разрабатывать темы для Keycloak на React и TypeScript. В этом докладе я расскажу: как мы интегрировали Keycloakify в наш проект, и какие были альтернативы, с какими подводными камнями столкнулись, что получилось в итоге, и как себя показало решение на практике.

1. Проблема: ограничения кастомизации фронтенда для Keycloak

Когда мы начали внедрять Keycloak, первым делом задумались: «А почему бы не использовать уже готовые формы аутентификации с нашего сайта? Сэкономим кучу времени, ведь все формы уже имеются».

Почему эта идея провалилась и интеграция через API не подошла? Если коротко: вход по API можно реализовать только для способа входа по логину и паролю, а нам были необходимы разные способы – смс, QR-код, соц.сети и др. Keycloak не поддерживает кастомные фронтенд-формы для флоу кроме Direct Access Grant (логин/пароль). Из этого следует невозможность организовать большинство сценариев, необходимых для современной формы авторизации, а именно:

  • Двухфакторная авторизация (SMS, TOTP, биометрия);

  • Вход через соцсети (Google, Apple и т.п.);

  • Согласие с политиками при регистрации.

После нескольких тысяч викли с командой Почти сразу мы пришли к выводу: нужно кастомизировать именно встроенные формы Keycloak.

Стандартные темы Keycloak: Freemarker и боль

Keycloak использует Apache Freemarker – шаблонизатор для Java.

Как он работает:

  • Все формы на пути авторизации (логин, регистрация и т. д.) – это отдельные страницы, реализованные в .ftl-шаблонах;

  • Keycloak рендерит их на сервере, подставляя данные (ошибки, ссылки, метаданные клиента) и отдаёт в браузер пользователя готовые HTML;

  • Можно добавлять свои шаблоны для новых страниц если есть необходимость.

Для примера так выглядит шаблон формы выбора способа входа:

Выглядит достаточно громоздко, хотя по сути даже не имеет логики, кроме:

  • сабмита HTML-формы;

  • отображения кнопок перехода на социальные провайдеры.

При поддержке такого шаблона могут возникать проблемы:

  • Отсутствует подсветка синтаксиса;

  • Условия для рендеринга прописаны прямо в вёрстке;

  • Названия CSS-классов подставляются из проперти-файла, и не всегда очевидно какое имя стиля получится в итоге;

  • Нельзя быстро перейти в импортированные в шаблоне файлы.

Забегая вперед: реакт компонент с подобным функционалом выглядит более привычно и понятно (особенно с учётом того что логику в таком подходе можно легко выносить в под-компоненты):

Таких шаблонов на текущий момент у нас уже порядка десяти. Большинство из них имеют примерно такую логику:

  • Поллинг урлов – статус аутентификации и др;

  • Логика группировки и скрытия отображения кнопок;

  • Клиентская валидация форм;

  • Таймеры.

И реализовать подключение скриптов в ftl-шаблон выглядит нетривиальной задачей – как в плане реализации, так и плане поддержки.

Проблемы, с которыми мы столкнулись при использовании Freemarker

1. Сложная кастомизация UI

  • Нельзя импортировать корпоративный UI-кит, чтобы переиспользовать на кнопки и поля ввода на форме. Да, придётся написать их с нуля. И неизвестно какие еще компоненты понадобятся в будущем;

  • Нет Hot Reload – при отладке после каждого изменения требуется перезапуск Keycloak;

  • CSS-стили приходится писать “в лоб”, без поддержки Sass/LESS.

2. Отсутствие современного Developer Experience

  • Нет TypeScript, ESLint, Prettier – только шаблоны с чистым JS (если сможете его встроить в <script>);

  • Синтаксические и семантические ошибки в шаблонах не подсвечиваются при разработке;

  • Нет компонентного подхода.

3. Проблемы с доставкой изменений

  • Тема – это часть серверного кода (скомпилированный .jar или файлы в /themes/);

  • Любое изменение требует перезагрузки Keycloak: сборка темы → перезагрузка Keycloak → проверка. В CI/CD это создаёт лишние шаги и замедляет процесс.

В общем и целом, Freemarker нам не подошел, так как требует глубокого погружения в новый шаблонизатор для Java, имеет медленный цикл разработки и достаточно “хрупкий” – обновление Keycloak может сломать кастомные шаблоны.

2. Почему мы выбрали Keycloakify

После анализа всех ограничений стандартных решений, Keycloakify показался нам оптимальным выбором. Ключевые преимущества, которые определили наш выбор:

1. Современный стек разработки

  • React + TypeScript – полная свобода в создании интерфейсов с использованием привычных инструментов;

  • Поддержка Vite/Webpack – мгновенная сборка и горячая перезагрузка в development-режиме;

  • Наличие storybook для удобной демонстрации и отладки;

  • Поддержка менеджеров пакетов – возможность использовать внешние зависимости, в том числе корпоративный UI-кит без изобретения своих костылей.

2. Простота интеграции

  • Минимальное погружение в Keycloak не нужно разбираться в Freemarker/JSP;

  • Готовая CLI (build-keycloak-theme) для сборки темы в .jar;

  • Прозрачный CI/CD – артефакт темы можно версионировать и деплоить как обычную зависимость для Java-проекта keycloak.

3. Клиентский рендеринг. С одной стороны клиентский рендеринг выглядит сомнительно,так как пострадает скорость загрузки страницы. Во-первых, из-за зависимости от производительности устройства пользователя. Во-вторых, из-за увеличения объема клиентского джаваскрипта. Но поскольку нагрузка на страницу входа обычно достаточно высока, при некорректной реализации ftl-шаблона можно случайно кратно увеличить нагрузку на сервер. В случае с Keycloakify:

  • Вся нагрузка по рендерингу страницы ложится на клиента;

  • В теле документа передается JSON-контекст (kcContext), который содержит только данные необходимые для отображении формы: название шаблона, состояние формы входа (ошибки валидации и т.д.) и ссылки для сабмита форм.

Как видно, клиент получает только чистые данные для отрисовки без готовой HTML-разметки. Это и есть секрет гибкости Keycloakify – полученный контекст можно рендерить с помощью реакта в любое отображение.

4. Сообщество и поддержка. Активно развивается (2000+ звёзд на GitHub), понятная документация с примерами и готовые решения для типовых задач.

5. Безопасность и производительность. Нет перегрузки сервера – рендеринг на клиенте. Есть совместимость с последними версиями Keycloak (20+). TypeScript-типы для kcContext в зависимости от текущей формы – защита от потенциальных ошибок.

Сравнение Freemarker и Keycloakify

3. Процесс внедрения Keycloakify

1. Начало работы: выбор стартера

Keycloakify предлагает несколько готовых шаблонов для быстрого старта:

  • React + Vite (рекомендуемый вариант)

  • Angular/Svelte-версии

  • React + Webpack (CRA) – мы выбрали этот вариант из-за проблем с совместимостью некоторых внутренних библиотек с Vite

Клонируем стартер (например, для React + Vite):

git clone https://github.com/keycloakify/keycloakify-starter
cd keycloakify-starter
npm install

На выходе получим проект со следующей структурой:

Из важных моментов в нашем проекте:

  • Файл index.tsx, в котором можно сделать мок kcContextа для отладки конкретной страницы в дев режиме:

  • KcPage.tsx – роутер со switch/case который и определяет какой реакт компонент рендерить для каждого названия ftl-шаблона:

  • Файл i18n.ts с локализациями, которые можно удобно расширять, например, вот так:

2. Базовая кастомизация

Уже можно приступить к кастомизации формы входа, для этого достаточно:

  • создать css-файл;

  • импортировать его в KcPage.tsx;

  • переопределить стандартные стили используемые в Keycloak, примерно так:

3. Глубокая кастомизация форм

Главной киллер-фичей Keycloakify является возможность легко превратить любой базовый ftl-шаблон в реакт-компонент. Для этого просто:

1. Выполняем команду: npx keycloakify eject-page

2. Выбираем нужную форму из списка;

3. Keycloakify генерирует: соответствующий файл с React-компонентом в папке src/login и код для вставки в роутер KcPage.

Например, так выглядит результат выполнения команды для шаблона формы подтверждения логаута: 

В таком подходе можно легко:

  • Импортировать компоненты из UI-библиотек;

  • Добавлять любой ts-код со своей логикой;

  • Работать с параметрами контекста Keycloak аналогично обычным шаблонам.

4. Создание кастомных форм

При работе с Keycloak иногда возникает необходимость написать свою форму авторизации, чтобы добавить логику, которой нет в стандартных формах Keycloak, и при этом сохранить Single Responsibility имеющихся форм – не тащить в них дополнительную логику или даже дополнительные вызовы апи, чтобы они продолжали выполнять только одну задачу.

В нашем случае первой такой формой стала форма выбора способа входа. На ней присутствует много логики – нужно отобразить 9 способов входа с особыми правилами группировки и сортировки, обрабатывать пользовательские согласия. Из-за этого форма явно напрашивалась на отделение от базового шаблона входа по телефону:

Добавить новую форму просто:

  • Договариваемся с бэкэндом о названии нового шаблона (login-select.ftl);

  • Создаём файл LoginSelect.tsx в папке с компонентами;

  • В роутинг в KcPage добавляем этот компонент для соответствующего названия шаблона;

  • В KcContext.ts добавляем типизацию kcContext для нового шаблона при необходимости.


5. Сборка и деплой

Для генерации .jar-файла с темой выполняем:

bash
npm run build-keycloak-theme

В результате выполнения команды создается .jar-файл с темой в папке ./build_keycloak.

В зависимости от настроек проекта Keycloakify может сгенерировать несколько файлов для разных версий Keycloak.

Далее мы просто копируем этот файл в наш Keycloak в CI:

cp -r /build_keycloak/* /opt/keycloak/custom-providers/

Выполняем мы это при деплое самого Keycloak на стенд, это дает нам возможность независимо от версии деплоя контролировать версию темы на стенде, а также откатывать тему без пересборки.

6. Готово

Если всё сделано правильно, то в админке Keycloak появится новая тема:

4. Трудности и их решение

При интеграции библиотеки мы столкнулись с рядом технических сложностей, которые пришлось решать нестандартными способами.

1. Проблемы с пакетным менеджером

Проблема: проект-стартер на React+Webpack не работает с npm из-за конфликтов транзитивных зависимостей.

Решение: мигрируем на yarn или pnpm – они резолвят зависимости. 

2. Зависимости из Maven Central

Проблема: Keycloakify загружает исходные jar файлы тем по прямой ссылке из repo1.maven.org, но с агентов CI доступ в интернет, конечно, закрыт. В исходниках Keycloakify это выглядит следующим образом:

Параметризировать эту загрузку и передать ссылку на корпоративное зеркало мавена нельзя, из-за чего сборка в закрытом контуре гарантированно упадет.

Решение:

  1. Собрать проект на машине с интернетом;

  2. Скопировать папку .cache из node_modules в свой проект на машину в корпоративной сети;

  3. Указать путь к этой папке через переменную среды:
    export XDG_CACHE_HOME=/path/to/cache

В результате агент будет брать зависимости из папки с кэшем вместо интернета. Саму папку с кэшем придется либо держать в репозитории (плохо, но быстро и удобно),либо, например, подключать отдельным докер образом – как вам удобнее.

3. Устаревший authChecker.js

Проблема: Keycloakify использует старую версию authChecker.js (из Keycloak v25), где есть баг появляющийся в Safari:

  • В скрипте authChecker есть логика таймера, который каждые две секунды проверяет: не залогинен ли пользователь;

  • Таймер останавливается в обработчике события window beforeunload – так, чтобы не выполнять эту логику в случае, если пользователь покидает страницу (например в случае успешного сабмита формы);

  • Из-за особенностей Safari событие beforeunload не происходит, остановка таймера не происходит;

  • Необходимо останавливать таймер в событии pagehide;

  • Некорректная отписка приводит к тому, что клиент повторно переходит по уже открывающейся ссылке, что приводит к ошибке.

PS: в Keycloak v26 баг исправлен, но Keycloakify пока использует старую версию скрипта.

Решение: вручную подменяем файл в node_modules перед сборкой:  

  1. Берем исправленную версию authChecker.js;

  2. Кладем его в src/patches;

  3. Перед билдом выполняем команду:

copyfiles -f ./patches/authChecker.js ./node_modules/.pnpm/keycloakify@11.3.4/node_modules/keycloakify/res/public/keycloakify-dev-resources/login/js

Риски: нет гарантий совместимости с будущими версиями. При обновлении Keycloakify текущая версия обновленного скрипта может не подойти.

5. Вместо вывода: стоит ли выбирать Keycloakify?

Пожалуй, можно сказать что Keycloakify претендует на роль стандарта для темизации Keycloak, взамен архаичным Freemarker Templates. 

100% – выбирайте Keycloakify, если Keycloak — ваш долгосрочный выбор. Решение идеально подходит для проектов, где Keycloak утвержден как стандарт для аутентификации.

Также, если требуется глубокая кастомизация UI: интегрировать корпоративную дизайн-систему, реализовать сложные формы (многошаговая регистрация, кастомный 2FA), полностью переработать UX потоков аутентификации.

Ну и конечно, если команда использует современный фронтенд-стек: React/Angular/Svelte, TypeScript, Vite/Webpack, CSS-модули или Sass. “Это база” – как говорится.

P.S: пока писал эту статью, успели столкнуться со следующими проблемами:

  • Даже в стабильных версиях библиотеки могут присутствовать баги, для которых приходится придумывать свои костыли;

  • Есть задержка между выходом очередной мажорной версии Keycloak и добавлением её поддержки в Keycloakify – обычно это 1-2 месяца совсем не критично, т.к. мажорные версии keycloak выходят не часто, но будьте готовы.