Привет! Я Андрей Баронский, бэкенд-тимлид в KTS.

Одно из ключевых направлений деятельности нашей компании — это аутсорс-разработка цифровых продуктов. При создании очередной системы мы хотим уделять больше времени и сил необходимым фичам для клиентов, а не настройке рутинного взаимодействия с юзерами, и для ускорения проработки основных пользовательских сценариев мы используем технологию Ory Kratos. В статье я расскажу, почему я рекомендую обратить на нее внимание и как с ней работать.

Для тех, кто впервые сталкивается с этим названием, дам немного контекста. Ory Kratos — это система API-first Identity и User Management. Она управляет всеми аспектами работы с пользователями, включая регистрацию, вход, восстановление пароля, многофакторную аутентификацию, верификацию данных и управление профилем. 

Иными словами, Ory Kratos берёт на себя рутинные технические задачи, предлагая готовое, гибкое и удобное в интеграции решение.

Оглавление

Сразу оговорюсь, что на Хабре уже есть статья, посвященная Kratos, и в ней довольно подробно разобрана основная функциональность технологии. Чтобы не повторяться, я не буду заострять внимание на деталях, описанных там. В своем материале я постараюсь больше сосредоточиться на опыте использования и показать, как можно на практике прикрутить Kratos к проекту.

Общая информация

Краткое описание

Ory Kratos распространяется под лицензией Apache-2.0, что даёт свободу его использования, модификации и распространения. Написанный на языке Go, он отличается высокой производительностью, стабильностью и масштабируемостью. Продукт активно развивается, регулярно получая обновления и новые функции. Простота внедрения и отличная документация делают его хорошим выбором для разработчиков.

Разумеется, существуют и альтернативные SSO, но мы отказались от них по своим причинам. 

Мы рассматривали Keycloak (24к звезд на гитхабе) и CAS (11к звезд), однако они нам не подошли, поскольку они написаны на Java и довольно неповоротливы в кастомизации, а ее выполнение требует большого и неудобного стека. К примеру, у Keycloak нет плагина для работы с OTP, и его пришлось бы дописывать самостоятельно.

Также мы смотрели в сторону Authelia (22к звезд) — как и Kratos (11,4к звеад), она написана на Go, и, в целом, тоже подходит под наши задачи. Но в итоге наш выбор пал на Kratos ввиду его простоты, исчерпывающей документации с примерами и возможностями кастомизации UI через разработку.

Почему Ory Kratos: еще несколько причин

Давайте рассмотрим остальные факторы, которые определили наш выбор:

  • наш собственный опыт — ранее мы уже экспериментировали с проектами Ory;

  • наш стандартный технологический стек — мы понимали, что можем без затруднений доработать приложение под собственные нужды;

  • возможность легко кастомизировать клиентскую часть приложения Kratos;

  • наличие нативной авторизации через клиентский API;

  • синергия Kratos с Hydra (провайдер OAuth 2.0 и ID Connect) и Oathkeeper (API gateway), которые имеют готовые компоненты для интеграции друг с другом без каких-либо доработок. Об этих технологиях я расскажу в следующих статьях;

  • удобная и понятная документация;

  • готовые Helm-чарты для развертывания приложения на k8s-кластере;

  • скорость, с которой можно было поднять пилотную MVP версию приложения;

  • легкость и изолируемость компонентов при дальнейшей замене.

Как работать с Ory Kratos

Верхнеуровневый обзор

Перед тем как перейти к подробному разбору конфигурации Ory Kratos, давайте обозначим сценарий, на котором будет строиться дальнейшее описание. Мы будем рассматривать стандартное приложение, которое включает:

  • SSO (Single Sign-On);

  • веб- и мобильный клиенты для взаимодействия с пользователем;

  • интеграцию с сервисом рассылок (например, для подтверждения email или отправки уведомлений);

  • возможную интеграцию с внешним SSO для обеспечения единой точки входа в приложение;

  • отдельный бизнес-слой, реализующий специфические сценарии приложения.

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

[BACKEND] Как собирать бэкенд

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

Когда мы знаем ответы на эти вопросы, мы можем описать в рамках приложения, в каких сценариях (flow) система будет использоваться. Всего на выбор Kratos дает шесть сценариев:

  • settings (изменение пользовательских данных);

  • recovery (восстановление доступов);

  • verification (подтверждение пользователя);

  • logout;

  • login;

  • registration.

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

kratos.config

selfservice:
 flows:
   verification:
     enabled: false

Пока что в  качестве примера мы рассматриваем клиентское приложение, в котором не используются верификация и восстановление доступов.

Примечание: кроме обмена данными между клиентом и сервером, backend-часть приложения выполняет редиректы. Для этого нужно указать путь до соответствующих разделов веб-приложения:

kratos.config

selfservice:
 default_browser_return_url: http://127.0.0.1:4455/welcome
 flows:
   login:
     ui_url: http://127.0.0.1:4455/login

Когда вы определились, какие сценарии вам понадобятся, нужно сформулировать, как будет происходить аутентификация пользователя в системе. Рассмотрим ее на примере «email+пароль». Нужно указать это в конфиге следующим образом:

kratos.config

...
selfservice:
  methods:
    password:
      enabled: true
...

Система также предлагает альтернативные методы аутентификации — они могут пригодиться, если вам не подходит вариант «логин+пароль»:

  • Passwordless + OTP/Magic Links;

  • Passkeys и WebAuthN;

  • Multi-factor authentication;

  • Social sign in.

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

kratos.config

secrets:
  cookie:
    - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
  cipher:
    - 32-LONG-SECRET-NOT-SECURE-AT-ALL

ciphers:
  algorithm: xchacha20-poly1305

hashers:
  algorithm: bcrypt
  bcrypt:
    cost: 8

К слову, длительность жизни сессий и OTP гибко настраиваются. Их также можно задать через конфиг.

У бэкенда приложения есть Public и Admin API. В конфиге также нужно указать соответствующие им адреса:

kratos.config

serve:
  public:
    base_url: http://kratos:4433/
  admin:
    base_url: http://kratos:4434/

Когда все предыдущие шаги выполнены, остается только сконфигурировать интеграцию с СУБД. Приложение поддерживает работу с реляционными базами данных (PostgreSQL, MySQL, SQLite и CockroachDB). Для ознакомления также доступна возможность работы в режиме in memory:

kratos.config

dsn: memory

Подробнее о сборке бэкенда см.:

[DB] Как собирать юзера для базы данных 

В Kratos пользователь — это ключевая сущность. Описать его можно с помощью документа JSON Schema. Для тех, кто раньше не сталкивался, JSON Schema — это декларативный язык для определения структуры и ограничений JSON.

Нам нужно описать основные атрибуты пользователя, указать идентификатор и способ логина. Это делается с помощью расширенных атрибутов ory.sh/kratos, в которых указываются системные зависимости.

Стоит обратить внимание, что title атрибутов пользователя будут использоваться в UI клиентского приложения. Еще при конфигурировании юзера важно помнить, что сервис Kratos отвечает только за работу с пользователем. Следовательно, в его атрибутах не нужно хранить какую-то специфическую бизнес логику, не относящуюся к домену. Храните только те данные, которые будут необходимы во всех компонентах системы. 

Рассмотрим конфигурацию на примере:

identity.schema.json

{
 "$schema": "http://json-schema.org/draft-07/schema#",
 "type": "object",
 "properties": {
   "traits": {
     "type": "object",
     "properties": {
       "username": {
         "title": "Username",
         "type": "string",
         "ory.sh/kratos": {
           "credentials": {
             "password": {
               "identifier": true
             }
           }
         }
       },
       "name": {
         "type": "object",
         "properties": {
           "first": {
             "title": "First name",
             "type": "string"
           },
           "last": {
             "title": "Last name",
             "type": "string"
           }
         }
       }
     }
   }
 }
}

Кроме указанных выше атрибутов, есть еще JSON-атрибуты metadata_public (доступные клиенту данные) и metadata_admin (данные, которые можно получить на уровне сервера). За консистентность данных нужно будет отвечать самостоятельно. Валидации этих параметров нет. 

После того, как мы разобрались с «анатомией» пользователя, в конфиге сервера нужно указать путь до схемы.

kratos.config

identity:
  default_schema_id: default
  schemas:
    - id: default
      url: file:///etc/config/kratos/identity.schema.json

Подробнее см.:

[FRONTEND] Как собирать фронтенд

Для реализации своего собственного пользовательского интерфейса есть 3 варианта репозиториев, но основе которых можно это сделать:

В данном примере мы будем использовать Next.js/React-приложение из репозитория. Я предлагаю рассмотреть, как указать переменные окружения для конфигурирования фронтенда. В качестве примера возьмем демо веб-приложение:

environment:
 - KRATOS_PUBLIC_URL=http://kratos:4433/
 - KRATOS_BROWSER_URL=http://127.0.0.1:4433/
 - COOKIE_SECRET=changeme
 - CSRF_COOKIE_NAME=ory_csrf_ui
 - CSRF_COOKIE_SECRET=changeme

Визуализация работы (preview)

Посмотрим, какого результата можно достичь текущей реализацией. На видео используется максимально приближенный к описанному конфиг:

Схема работы (исходник здесь):

Углубленный обзор

Мы познакомились с базовыми сценариями IdM-решения, и теперь я предлагаю рассмотреть дополнительные возможности инструмента, которые могут пригодиться при интеграции Kratos в проект.

Интеграция с сервисом рассылок

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

1. Добавляем шаг подтверждения почты во flow верификации:

kratos.config

  flows:
    verification:
      enabled: true

identity.schema.json (строки 16, 17):

  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "format": "email",
          "title": "E-Mail",
          "minLength": 3,
          "ory.sh/kratos": {
            "credentials": {
              "password": {
                "identifier": true
              }
            },
            "verification": {
              "via": "email"
            }
          }
        }
      }
    }
  }

2. Конфигурируем интеграцию с сервисом рассылок. Это может быть реализовано с помощью протокола SMTP. Для того, чтобы уменьшить объем кода и избыточного контекста, я опущу детали работы с аутентификацией. Отмечу только, что возможности ее настройки в проекте есть.

kratos.config

courier:
  smtp:
    connection_uri: smtps://test:test@mailslurper:1025

Также в Kratos есть возможность шаблонизации сообщений через Go template engine. Если вы с ними ранее не сталкивались с этим инструментом, тут можно с ним ознакомиться. В этом документе указаны переменные, которые будут переброшены при генерации тела письма.

email.body.gotmpl

Hi, please verify your account by code:

{{ .VerificationCode }}

Если же интеграция с сервисом рассылок через SMTP вам не подходит, у вас свой сервер или вы пользуетесь готовым решением, где работа с шаблонами писем уже реализована с интеграцией через HTTP API, то в системе также предусмотрена альтернативная интеграция через HTTP.

kratos.config

...
courier:
 delivery_strategy: http
 http:
   request_config:
     url: https://api.sendsrv.com/mail/send
     method: POST
     body: file:///etc/config/kratos/mail.template.jsonnet
     headers:
       "Content-Type": "application/json"
...

В случае HTTP-интеграции тело запроса шаблонизируется с помощью конфигурационного языка jsonnet. Аналогично при генерации тела запроса будет передан ctx, из которого можно будет достать необходимые переменные и данные о пользователе. Посмотрим на примере интеграции с сервисом рассылок Unisender:

http_courier_template

function(ctx) {   
 message: {
   recipients: [
     {
       email: ctx.recipient,
       substitutions: {
         RECOVERY_CODE: ctx.template_data.recovery_code,
       },
     },
   ],
   from_email: "no-reply@yourapp.ru",
   template_id: "00000000-0000-0000-0000-000000000000"
 }
}

В итоге получаем следующий результат:

Подробнее см. документацию:

Пара слов про API-авторизацию (native/browser)

Так как помимо обмена данных Kratos выполняет редиректы, у продукта есть отдельный API для native-клиентов. Разработчики рекомендуют воздержаться от использования native-клиентов в браузерных приложениях, поскольку это это открывает брешь в обороне для CSRF-атак.

Webhooks и как их использовать

В продукте есть возможность кастомизации логики с помощью внешних интеграций через webhook. Для всех основных flow есть хуки before и after, которые позволяют реализовать все необходимые сценации. 

При описании webhook есть возможность сконфигурировать их поведение: до или после выполнения сценария, блокирующий или нет. Это может пригодиться, например, если после успешной регистрации пользователя вам нужно автоматически добавлять его в списки рассылок в отдельном сервисе. Или, наоборот, усложнить регистрацию в систему, добавив дополнительные шаги проверок в других компонентах. Тело запроса описывается аналогично примеру с HTTP-интеграцией рассылок с помощью Jsonnet. 

Более того, в Kratos даже есть возможность обновлять атрибуты identity через webhook — как UI-параметры пользователя, так и metadata (публичные и приватные). 

Рассмотрим ситуацию, при которой мы хотим дополнительно проверить заявку на регистрацию пользователя в другом компоненте нашей системы. Для этого воспользуемся блокирующим after-хуком с парсингом тела ответа, чтобы заводить пользователей в систему только после прохождения проверки.

Расширим конфиг сервиса настройками flow регистрации:

kratos.config

selfservice:
 flows:
   registration:
     after:
       password:
         hooks:
           - hook: web_hook
             config:
               url: http://wiremock:8443/webhook
               method: "POST"
               body: "file:///etc/config/kratos/reg_webhook.jsonnet"
               response:
                 parse: true

Приведем пример ответа, который будет обрабатывать Kratos и информировать о проблеме с регистрацией, и замокаем ответ на вебхук. В данном случае "instance_ptr": "#/traits/email" (4 строка) является атрибутом, с которым ассоциируется ошибка:

{
   "messages": [
       {
           "instance_ptr": "#/traits/email",
           "messages": [
               {
                   "id": 12356,
                   "text": "Мы решили не пускать тебя в систему!(",
                   "type": "error",
                   "context": {
                       "value": "Мы решили не пускать тебя в систему!("
                   }
               }
           ]
       }
   ]
}

На видео можно посмотреть пример блокирующего вебхука c информацией об ошибке в UI:

Подробнее см. документацию:

Получение пользовательской информации в сервисах бизнес-логики

Можно, конечно, реализовать мидлвар в наших микросервисах для получения информации о пользователях, но я предлагаю использовать для этого Oauthkeeper — AGW, за которым можно скрыть все компоненты приложения. В нем же можно сконфигурировать, какие пользовательские данные мы будем передавать в микросервисы. Так мы избавляемся от необходимости дублировать интеграцию с Kratos в каждом сервисе и можем легко настраивать это на стороне AGW.

OAUTH2

Стоит отметить, что Ory Kratos из коробки поддерживает вход через внешние сервисы с помощью OAuth2 и OpenID Connect. Вам остается только сконфигурировать их. Не буду погружаться в детали, просто оставлю ссылку на соответствующий раздел документации, если вам интересно узнать об этом больше.

Аутентификация по нескольким id

В Kratos есть возможность настроить несколько identifiers и указать различные варианты аутентификации для них. Зачем это нужно? Например, для того, чтобы пользователь мог логиниться в системе не только по адресу электронной почты, но и по номеру телефона. Рассмотрим этот пример подробнее.

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

Выше мы уже добавили возможность входить в наше приложение через связку «email + password». Добавим также связку «phone + password». Для этого нужно расширить схему пользователя. Доработаем ее:

Identity.schema.json

 {
 "$id": "https://schemas.ory.sh/presets/kratos/identity.email.schema.json",
 "title": "Person",
 "type": "object",
 "properties": {
   "traits": {
     "type": "object",
     "properties": {
       "phone": {
         "type": "string",
         "format": "tel",
         "title": "Phone number",
         "ory.sh/kratos": {
           "credentials": {
             "password": {
               "identifier": true,
             }
           }
         }
       }
     }
   }
 }
}

Приятные мелочи

Отдельно хочу подсветить небольшие детали, которые нередко упрощают жизнь при работе с Kratos.

  • Во-первых, в системе есть возможность запускать Cleanup jobs, чтобы чистить устаревшие сессии и освобождать данные с диска. Подробнее про них можно почитать в документации.

  • Во-вторых, разработчики добавили возможность деактивации пользователей. Здесь тоже оставлю ссылку на соответствующий раздел.

  • В-третьих, в Kratos предусмотрена миграция пользователей через import, причем при миграции можно сохранять их прежние пароли. По традиции, детали в документации.

  • Можно задать ограничение на уровне конфига — не более одной активной сессии на пользователя.

  • К слову об активной поддержке и расширении функциональности: пока я писал эту статью, появилась возможность реализовать passwordless flow для логина и регистрации через sms.

Небольшое (ленивое) нагрузочное 

Предлагаю посмотреть, как Kratos справляется с нагрузками. Здесь я не преследую цели провести полноценное испытание в условиях, максимально приближенных к реальным, а просто тестирую технологию «в вакууме», чтобы проверить основные паттерны.

Проверим систему двумя простыми сценариями. Тестировать будем следующую конфигурацию:

  • 1 запущенный инстанс приложения на k8s-кластере;

  • порты проброшены на локальную машину;

  • лимиты по памяти —  512 MB.

Первый сценарий нагрузки

N пользователей (в нашем случае — 50) проходят flow аутентификации из двух шагов: создание flow и отправка данных (логин + пароль).. Задержка между шагами — от одной до трех секунд. Разумеется, в действительности типовая нагрузка будет не такой, но сценарий дает нам представление о том, как сервис будет справляться с наплывом новых пользователей в начале своей работы.

Получаем следующие результаты:

  • С заданной конфигурацией Kratos успевает обрабатывать чуть больше 20 запросов в секунду.

  • Больше половины полученных запросов на пике обрабатываются быстрее, чем за две секунды; 95 % запросов обрабатываются быстрее, чем за четыре секунды.

Второй сценарий нагрузки

Пользователь проходит flow аутентификации и начинает в цикле запрашивать информацию о профиле. Также этот запрос может использовать API Gateway для проверки сессии пользователя. Именно такой и будет основная нагрузка на сервис.

Получаем следующие результаты:

  • Как и предполагалось, пик нагрузки происходит в момент, когда пользователи активно логинятся: 100 одновременных юзеров входят в систему примерно за 20 секунд, на протяжении которых RPS не поднимается выше 100.

  • После того, как все пользователи получают активную сессию, проходит пик нагрузки. При 100 пользователях Response Time для 95% запросов не превышает 400 мс. RPS при этом держится чуть меньше 600.

  • Минимальное время обработки запроса — 30 мс.

Выводы

Судя по результатам, «узкое горлышко» системы — это момент активного наплыва пользователей, которые одновременно хотят залогиниться. С запросами информации о профиле Kratos справляется намного эффективнее при прочих равных. Горизонтальное масштабирование системы (увеличение количества инстансов) может помочь решить эту проблему в критические моменты.

Недостатки Kratos

Разумеется, в работе мы столкнулись и с негативными аспектами системы. Поговорим про некоторые из них.

  • Нет ограничений на отправку кода верификации и восстановления доступов, и сделано это не будет. Нужно самостоятельно решать вопрос с ограничениями на стороне AGW и других интеграций.

  • Нет готовой admin-панели. Ее не очень сложно реализовать самостоятельно с помощью кодогенерации поверх схемы базы данных; к примеру, мы используем django + sdk для интеграции с API. Однако отсутствие готовой панели все же несколько огорчает.

  • Заблокированным пользователям может быть неочевидно, что они заблокированы. Они могут начать восстанавливать пароль или попробовать залогиниться в систему с помощью OTP, получить код от системы, и только при его вводе узнать о блокировке. Можно попробовать это решить с помощью блокирующего before-вебхука с парсингом ответа.

Границы применимости и заключение

Мы испытали Kratos на нескольких проектах и сформулировали ряд выводов, касающихся удобства системы. Я смело рекомендую пользоваться этой технологией, если:

  • вы не хотите тратить время разработчиков на создание своих кастомных велосипедов и согласны пользоваться сценариями, предложенными системой (или готовы вводить кардинальные изменения системы через fork проекта);

  • вас устраивает заложенный UI готового решения;

  • вы готовы завести отдельное реляционное хранилище пользователей и можете синхронизировать его с мастер-данными;

  • вам не нужна интеграция с AD (если нужна, то решение вам не подойдет).

На случай, если система вам интересна и вы захотели протестировать ее самостоятельно, оставляю ссылки на полезные материалы:

  1. Туториал по быстрому стартусоответствующий раздел репозитория на GitHub)

  2. API

  3. Структура валидатора конфига (берите конфиг из Quickstart и уже дальше докручивайте при необходимости)

  4. Helm chart, который можно использовать для начала

  5. Postman collection

Если у вас остались вопросы или какой-то из разделов показался не до конца раскрытым, то приходите в комментарии, с радостью обсудим.

Более конвенциональным разработчикам, которые используют Keycloak, я рекомендую почитать статью моего коллеги из DevOps-юнита о том, как прикрутить к нему Firezone. Также советую ознакомиться с материалами из нашего блога для бэкендеров и DevOps-инженеров:

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

Удачи!

Комментарии (3)


  1. qbertych
    30.12.2024 16:53

    Ого, какой изумруд под конец года! Похоже пока что это бесспорный лидер по соотношению количества лайков к количеству просмотров ;)


  1. Acidter
    30.12.2024 16:53

    В конце статьи в списке ссылок первые две имеют один адрес.


    1. baronskiy_a Автор
      30.12.2024 16:53

      Поправил ссылку. Спасибо, что заметили!