Вступление
Ory Kratos - современный cloud native сервер идентификации с поддержкой PassKeys, MFA, FIDO2, TOTP, WebAuthn, с возможностью управления профилями, схемами пользователей, входом через внешние сервисы, регистрацией, восстановлением аккаунта, с поддержкой passwordless входа. Написан на Go, headless, API-first.
В этой статье будет рассмотрен функционал Ory Kratos и опыт использования.
Не будет рассмотрено: деплой и использования в production среде, для этого есть документация, ссылки на которую можно найти в статье либо в конце.
Зачем?
При написании множества пет-проектов всегда остро вставал вопрос авторизации и аутентификации пользователей и как-то раз решил написать свой универсальный сервис идентификации, который устроил бы меня для использования в петах и возможно не только в них.
Со временем пришло осознание что требования к каждому пету достаточно сложно натянуть на модель юзеров текущей реализации такого сервиса (могла быть авторизация по эмейлу, где-то по номеру, а где-то еще и oauth2/oidc хотелось), поэтому присматривался к нескольким готовым решениям, которые покрыли бы мои хотелки. Исходя из этого выделил несколько требований к такому коробочнуму решению:
возможность развернуть на своем сервере
возможность выбрать идентификатор для пользователей или сделать их несколько (например уникальные адрес эл. почты и номер телефона, по которым можно залогиниться)
возможность безболезненно присобачить OAuth2/OpenID Connect провайдеров, с уже готовыми интеграциями
бесплатно и open source, с активным комьюнити и много звездочек в github (куда же без этого)
возможность создания нескольких типов юзеров, с разным набором полей и методами авторизации
headless, с наличием HTTP/gRPC API
Были рассмотрены:
Firebase, auth0 (платно, нету self hosted решения)
Keycloak (сразу показался огромным динозавром, только зайдя в админку понял что большинство что он предлагает мне не понадобится). Можете кидать в меня камнями за такое непредвзятое решение, но для петов хотелось что-то небольшое и простое
SuperTokens, FusionAuth (нету возможности авторизации по разным идентификаторам)
Ory Kratos. На нем я и остановился, на первый взгляд по документации все хотелки были и проект совсем недавно получил первую мажорную версию, в отличии от других проектов Ory. Другие решения не рассматривал в угоду моего нездорового взгляда на непопулярные решения, буду рад узнать о других от читателей этой статьи.
Браузерное vs нативное API
В Kratos выделили два API для соответствующих приложений:
Браузерное - для приложений, в которых конечный пользователь работает с браузером. При логине будет выдана кука сессии и анти CSRF токен, при получении сессии из браузера (react, vue, angular и.т.д.) с помощью метода
toSession()
не будет дополнительных сетевых вызововНативное - для приложений которые общаются с Kratos исключительно по API (также этот вид API должен использоваться для мобильных приложений). При работе с этим API будет выдан токен сессии вместо куки, он эквивалентен куке и при вызове метода
toSession()
будет получена та же сессия
Использовать нативное API в браузере не получится физически, Kratos вернет ошибку:
"messages": [
{
"id": 4000001,
"text": "The HTTP Request Header included the \"Cookie\" key, indicating that this request was made by a Browser. The flow however was initiated as an API request. To prevent potential misuse and mitigate several attack vectors including CSRF, the request has been blocked. Please consult the documentation.",
"type": "error"
}
]
Модель пользователя (Identity)
Пользователей в Kratos называют Identity, реализующие стандарт JSON schema для описания трейтов (атрибутов), которые можно изменять и дополнять.
У каждой identity теоретически должна быть своя JSON схема, это могут быть как живые пользователи (например можно описать схему для пользователя client
и manager
), так и системные аккаунты, роботы и.т.д. По умолчанию будет использоваться та схема, которая будет указана в конфиге схемой по умолчанию, и все операции (далее flows) с пользователями будут работать именно с этой схемой (авторизация, регистрация, восстановление и.т.д).
Создать identity со схемой не указанной по умолчанию можно только через админский API с помощью метода POST /admin/identities
или с помощью приглашений.
В конфиге установка схемы по умолчанию выглядит следующим образом:
# kratos.yml
identity:
# будет использоваться по умолчанию для всех flows
default_schema_id: user
schemas:
- id: user
# также можно указать схему в base64 или указать url до схемы
url: file:///etc/config/kratos/schemas/user.schema.json
- id: organizer
url: file:///etc/config/kratos/schemas/organizer.schema.json
Трейты (атрибуты пользователя)
Трейты это атрибуты каждой отдельной identity, в каждой JSON схеме должен быть описан как минимум один трейт для идентификатора (например email или username) и один трейт который будет использоваться для верификации и восстановления доступа (верификацию можно отключить в конфиге).
На примере схемы user
можно увидеть как описываются трейты, в данной ситуации пользователь имеет один идентификатор - email и авторизацию по паролю (подробнее можно посмотреть в документации):
// user.schema.json
{
"$id": "user",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "User",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
// заголовок который можно использовать для отрисовки в GUI
"title": "E-Mail",
"minLength": 3,
"ory.sh/kratos": {
"credentials": {
"password": {
// email является идентификатором для входа с паролем
"identifier": true
}
},
"verification": {
"via": "email"
},
"recovery": {
"via": "email"
}
}
},
"name": {
"type": "object",
"properties": {
"first": {
"title": "First Name",
"type": "string"
},
"last": {
"title": "Last Name",
"type": "string"
}
}
}
},
"required": [
"email"
],
"additionalProperties": false
}
}
}
Потоки? (flows)
Это двухэтапные процессы регистрации и управления аккаунтом. В Kratos есть следующие flows:
регистрация
авторизация
выход
настройки профиля (редактирование трейтов)
верификация номера телефона/адреса эл. почты
восстановление
Они делятся на 2 запроса: создание и отправление (submit) flow. При создании flow возвращается список трейтов из схемы пользователя по умолчанию, для того чтобы отрисовать их в GUI.
Если вам нужно выполнить что-то во время создания или подтверждения flow, то это можно настроить в конфиге:
selfservice:
flows:
login:
before: # при создании flow
hooks:
- hook: hook_1
after: # при подтверждении flow
hooks:
# выполнится для всех методов аутентификации
# кроме аутентификации по паролю
- hook: hook_2
password:
hooks:
# выполнится только для аутентификации по паролю
- hook: hook_3
Подробнее про хуки можно прочитать тут.
Чтобы получить информацию о сессии из Go можно использовать следующий HTTP middleware:
package httpapi
import (
"errors"
"fmt"
"net/http"
"context"
kratos "github.com/ory/kratos-client-go"
)
func newAuthMiddleware(client *kratos.APIClient) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("ory_kratos_session")
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
ctx := r.Context()
// Получаем сессию из Kratos
session, resp, err := client.FrontendApi.
ToSession(ctx).
Cookie(cookie.String()).
Execute()
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
if resp.StatusCode != http.StatusOK {
w.WriteHeader(http.StatusUnauthorized)
return
}
if !session.GetActive() {
w.WriteHeader(http.StatusUnauthorized)
return
}
identity := session.GetIdentity()
// если текущий юзер не соответствует схеме с id = organizer
if identity.SchemaId != "organizer" {
w.WriteHeader(http.StatusForbidden)
return
}
// положить identity id в контекст для будущего использования
ctx = context.WithValue(ctx, "identity_id", identity.Id)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
}
Если у вас много сервисов и ходить каждый раз по сети становится дорого можно использовать Ory Oathkeeper как прокси для аутентификации. Прокси будет конвертировать полученную сессию в токен сессии и далее отправлять его в заголовке дальше по сервисам, вам остается только вызвать метод sessionToken()
, передав в него полученный из заголовка токен. Подробнее про Oathkeeper можно почитать в документации.
Вход через внешние сервисы
Kratos из коробки поддерживает вход через внешние сервисы с помощью OAuth2 или OpenID Connect, остается их только сконфигурировать. Далее покажу как подключить вход на примере twitch
, для этого нужно в переменную окружения SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS
записать конфиг в формате JSON массива:
[
{
"id": "twitch",
"provider": "generic",
// взять в ЛК twitch dev.twitch.tv/console
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"issuer_url": "https://id.twitch.tv/oauth2",
// также можно указать в base64 или url на jsonnet
"mapper_url": "file:///etc/oidc/oidc.twitch.jsonnet",
"scope": [
"openid",
"user:read:email"
],
"requested_claims": {
"id_token": {
"email": {
"essential": true
},
"email_verified": {
"essential": true
}
}
}
}
]
Чтобы смаппить данные (claims), возвращаемые конкретным провайдером нужно использовать маппер Jsonnet, путь на маппер указывается в конфиге к провайдеру в поле mapper_url
(пример выше). Маппер для twitch:
// oidc.twitch.jsonnect
ocal claims = {
email_verified: false,
} + std.extVar('claims');
{
identity: {
traits: {
// возвращаем адрес эл. почты из полученного объекта claims,
//если она подтверждена в twitch
[if 'email' in claims && claims.email_verified then 'email' else null]: claims.email,
},
},
}
Список всех поддерживаемых провайдеров и документация по тому, как добавить их в проект: тык.
Метрики
Все Ory сервисы, включая не только Kratos, имеют метод для получения prometheus метрик: /metrics/prometheus
Трейсинг
Как настроить трейсинг можно найти в документации, на момент написания статьи поддерживается только Jaeger.
Другие проекты Ory
Помимо identity сервиса у Ory есть три решения:
Ory Oathkeeper - zero trust прокси для авторизации входных запросов
Ory Keto - управление правами
Ory Hydra - OpenID Connect/OAuth2 провайдер
Полезные ссылки
Быстрый старт
HTTP API документация
Документация по модели Identity
Документация по Flows
Приглашение пользователей
Сессии
Шаблон конфига
Настройка SMTP
Вход через внешние сервисы
Paswordless аутентификация
Многофакторная аутентификация
Рекомендации по деплою в продакшен
Трейсинг
Zero Trust прокси
Kratos Go клиент
Ory Go клиент - клиент ко всем сервисам Ory в одном пакете