
Всем привет! Меня зовут Артем, я фронтенд разработчик в Банки.ру. Недавно перед нашей командой встала задача внедрить SSO-аутентификацию через Keycloak для клиентских сервисов. Помимо базовой настройки, важно было полностью переработать стандартные формы входа, чтобы они соответствовали корпоративному стилю и UX-требованиям.
Эта статья будет полезна фронтенд-разработчикам, которые хотят кастомизировать интерфейс Keycloak, не жертвуя современным стэком, скоростью разработки и здравым рассудком. А также командам, которые находятся на этапе выбора решения для авторизации из имеющихся на рынке. Мы разберём практические шаги внедрения, покажем код и честно расскажем о подводных камнях, с которыми столкнулись сами.
О чем поговорим в статье:
Итак, с какими проблемами мы столкнулись:
Ограничения стандартных тем 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 это выглядит следующим образом:

Параметризировать эту загрузку и передать ссылку на корпоративное зеркало мавена нельзя, из-за чего сборка в закрытом контуре гарантированно упадет.
Решение:
Собрать проект на машине с интернетом;
Скопировать папку .cache из node_modules в свой проект на машину в корпоративной сети;
Указать путь к этой папке через переменную среды:
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 перед сборкой:
Берем исправленную версию authChecker.js;
Кладем его в src/patches;
Перед билдом выполняем команду:
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 выходят не часто, но будьте готовы.