Files
han-app/modules/module-08-keycloak.md
T

34 KiB
Raw Blame History

module-08. Проектная спецификация keycloak

Статус: целевая production-спецификация MVP; реальный SMS provider не входит в scope.
Источники: README.md, arch-00-glossary.md, arch-01-system-architecture.md, arch-02-api-contracts.md, arch-03-docker-compose-blueprint.md, arch-04-settings-and-content.md, arch-05-agent-development-process.md, module-01-api-backend.md, module-02-frontend-test-site.md, module-03-nginx.md, ../../HAN_chat/deploy/init-managed-postgres.py.

1. Назначение и границы

Keycloak — единственный IdP HAN Chat. MVP предоставляет регистрацию/вход только по подтверждённому номеру телефона и OTP, OIDC tokens, refresh/logout, discovery/JWKS и защиту auth flow.

Keycloak отвечает за:

  • realm, users, credentials, auth sessions и token lifecycle;
  • Authorization Code Flow with PKCE для Expo web/iOS/Android;
  • нормализацию/уникальность телефона и claims;
  • OTP authenticator/SPI, mock verification и продуктовые limits;
  • brute-force, sessions, logout/revocation;
  • keys/JWKS rotation и health/metrics.

Не отвечает за:

  • api-backend bootstrap/consents/UserIdentity;
  • App DB/profile/chat и CRM sync;
  • API service-to-service tokens;
  • пользовательскую UX-сессию;
  • реальную отправку SMS в MVP.

Реальный SMS provider — строго extension point/TBD. Mock code является секретом окружения, не контентом UI и не логируется.

2. Топология и публичный URL

Keycloak работает за единственным root nginx:

Client HTTPS https://tohin.ru/auth/*
  → nginx TLS termination
  → HTTP keycloak:8080 в закрытой Docker network
  → managed PostgreSQL schema keycloak по TLS

Публичный issuer обязан быть стабильным:

https://tohin.ru/auth/realms/han-chat

OIDC discovery:

https://tohin.ru/auth/realms/han-chat/.well-known/openid-configuration

JWKS — URI из discovery. api-backend проверяет iss, audience, signature, exp/nbf и sub, не вызывает Admin API в hot path.

3. Версия, image и providers

  • Keycloak Quarkus distribution, поддерживаемая LTS/stable версия, закреплённая image digest.
  • PostgreSQL JDBC driver из image.
  • Custom Java provider JAR для phone OTP authenticator/settings bridge/counters.
  • Сборка provider reproducible, зависимости pinned, SBOM/signature/security scan.
  • Build-stage выполняет kc.sh build; runtime image immutable/non-root.
  • Перед upgrade читаются Keycloak migration notes и SPI compatibility.

Версия Keycloak фиксируется в deployment manifest; latest запрещён.

4. Realm и clients

Realm: han-chat. Master realm не используется приложением.

4.1. Public frontend client

Канонический client id:

han-chat-frontend

Настройки:

  • public client; client authentication off;
  • standard flow on;
  • Authorization Code + PKCE S256 обязательно;
  • implicit flow off;
  • direct access grants/password grant off;
  • service accounts off;
  • device flow off, если не нужен;
  • consent screen Keycloak не заменяет продуктовые согласия API;
  • exact redirect URIs и web origins;
  • full scope allowed off; только назначенные scopes/mappers.

Примеры redirect URI должны перечисляться отдельно:

https://tohin.ru/auth/callback
han-chat://auth/callback
<Expo native scheme/callback, exact value после сборки>

Production redirect URI задаются exact; wildcard не используется до отдельного security review. Development localhost origins/redirects находятся в отдельном dev realm/client либо profile и запрещены production.

4.2. API audience

Audience:

han-chat-api

Client scope/audience mapper добавляет aud=han-chat-api в access token frontend. api-backend не принимает token только по azp без audience.

4.3. Optional confidential client

han-chat-backend можно импортировать disabled/optional для будущих admin/ops S2S:

  • client authentication on, service account only при явном включении;
  • secret не хранится в realm export;
  • минимальные roles;
  • не используется между текущими сервисами и не требуется для JWT validation;
  • не участвует в пользовательском hot path.

Internal API по-прежнему используют service tokens из arch-02.

5. OTP-only phone flow

5.1. Browser flow

Отдельный flow han-phone-otp-browser:

  1. Cookie/SSO authenticator проверяет действующую Keycloak session.
  2. При отсутствии session показывается форма телефона.
  3. Phone Identity Authenticator нормализует номер.
  4. Проверяются realm brute-force и product send limits.
  5. Создаётся/находится user по canonical phone identity.
  6. Phone OTP Challenge инициирует mock/provider send.
  7. Показывается форма OTP.
  8. Проверяются TTL/attempt limits/constant-time hash or mock compare.
  9. При успехе user enabled/phone verified, flow завершается code.
  10. Frontend меняет code+verifier на tokens.

Password form, registration password, reset password, email OTP, social login и magic link отсутствуют.

5.2. Регистрация/find-or-create

До выдачи OTP новый user может существовать как short-lived pending identity либо создаваться после успешной проверки. Предпочтительное решение:

  • normalized phone reservation/counter создаётся в SPI store;
  • permanent Keycloak user создаётся/активируется только после успешного OTP;
  • concurrent flow защищён unique phone index/transaction;
  • abandoned pending challenges очищаются TTL.

Если Keycloak storage не позволяет безопасный custom unique index в managed schema, user создаётся disabled с deterministic username и очищается job; точная реализация покрывается concurrency tests.

5.3. Required actions

Используются только при реальной необходимости:

  • VERIFY_PHONE — если user импортирован/номер изменён вне текущего verified flow;
  • UPDATE_PHONE — будущий controlled flow с повторной OTP;
  • terms/product consents не required action: версии и факт согласия хранит api-backend.

Required actions не должны предлагать пароль/email. После phone OTP обычный вход завершается без лишнего profile screen.

6. Нормализация и уникальная identity

Телефон парсится libphonenumber:

  • Unicode digits/NFKC input normalization;
  • default region RU допустим только для национального ввода; международные номера поддерживаются по product policy;
  • canonical storage/claim — E.164, например +79001234567;
  • invalid/impossible number отклоняется до send;
  • отображение только masked;
  • canonical phone comparison exact.

Рекомендуемая модель:

  • username = canonical E.164 либо irreversible deterministic identifier;
  • user attribute phone_number = E.164;
  • phone_number_verified=true;
  • unique phone enforced storage-level, не только pre-check;
  • email nullable/не используется.

Утечка существования номера запрещена: initiate/challenge возвращают одинаковый внешний текст/timing class для нового/существующего пользователя. Один verified phone соответствует одному active sub. Merge/reassignment — отдельная administrative policy, не автоматический side effect login.

Изменение телефона требует re-auth + OTP нового номера и invalidation sessions/tokens по policy. api-backend обновляет cached phone при следующем bootstrap/login claim; прямого вызова Keycloak DB нет.

7. Mock OTP

Env:

KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_CODE=<secret>

Правила:

  • mock разрешён MVP production-like только как явно принятый риск;
  • пустой/default 1234 запрещён startup policy для production-like, если не согласован secret;
  • code не входит в realm import, frontend config, HTML hint, API response, logs, metrics, traces или audit;
  • сравнение constant-time;
  • challenge всё равно имеет TTL, max verify attempts и counters, чтобы flow был близок production;
  • code не сохраняется per-user в открытом виде;
  • UI сообщает только «тестовый режим», без кода;
  • KEYCLOAK_OTP_MOCK_ENABLED=false при отсутствии configured provider делает OTP flow fail-closed/not-ready, а не пропускает проверку.

Реальный provider interface:

interface OtpDeliveryProvider {
  DeliveryResult send(E164Phone phone, String otp, Duration ttl, Correlation ctx);
}

Будущий provider обязан вернуть provider_message_id; raw OTP не логируется. Выбор provider, template, sender, delivery status webhook и vendor credentials — TBD.

8. OTP challenge и counters

Даже в mock:

  • challenge id random ≥128 bit;
  • OTP не хранится raw; production-generated code — keyed hash/HMAC с challenge salt/pepper;
  • TTL (предлагается 5 минут) — technical security parameter;
  • one-time use; success atomically consumes challenge;
  • max verification attempts per challenge;
  • resend invalidates либо version-binds предыдущий challenge;
  • replay/parallel verify безопасны;
  • destination stored masked/hash where possible.

Audit fields по arch-05: provider message id (для mock — synthetic non-secret), sent_at, destination_masked, otp_hash/reference, attempts, outcome. Никогда raw code.

8.1. Product send limits bridge

SPI вызывает:

GET http://api-backend:8000/internal/settings/v1/otp
Authorization: Bearer ${KEYCLOAK_SETTINGS_BRIDGE_TOKEN}

Ответ:

{
  "max_send_attempts_per_24h": 3,
  "min_seconds_between_attempts": 30,
  "version": "2026-07-10T08:00:00Z",
  "cache_ttl_seconds": 60
}

Это единственный путь к otp.phone.*; Keycloak не получает GRANT на han_app. SPI поддерживает ETag/cache, single-flight refresh. Bridge down:

  • использовать last-known-good до bounded max stale;
  • если cache пуст/слишком стар — fail-closed для send;
  • verify уже выданного challenge может продолжаться по snapshot, с которым challenge создан.

Token name точно KEYCLOAK_SETTINGS_BRIDGE_TOKEN, endpoint точно /internal/settings/v1/otp.

8.2. Где хранятся counters

Решение MVP: counters/challenges хранятся в Keycloak-owned PostgreSQL tables/provider storage в схеме keycloak, а не в Redis API и не в han_app.

Причины:

  • durable across restart;
  • одна transaction для reserve/send-attempt/consume;
  • не добавляет Keycloak credentials к общему Redis;
  • соответствует границе «счётчики в зоне Keycloak/SPI».

Используются phone HMAC, не E.164 в key/index для rate data. Tables provider-owned создаются versioned migration provider-а, не ручным DDL-on-start.

Минимальные records:

  • han_otp_challenge: id, phone_hmac, otp_hash/mock marker, created/expires/consumed, verify attempts, settings version, provider id/status;
  • han_otp_send_counter: phone_hmac, window_start, count, last_sent_at;
  • han_otp_security_event: append-only minimal outcome/retention.

Indexes: unique active challenge policy, (phone_hmac,window_start), (expires_at). Cleanup bounded job. Доступ только keycloak_user.

9. Brute-force и abuse

Слои:

  1. nginx /auth IP rate limit (NGINX_RATE_LIMIT_AUTH);
  2. Keycloak realm brute-force detection;
  3. SPI product send limits per phone HMAC;
  4. verify-attempt limit per challenge/phone/IP hash;
  5. cooldown after repeated failures;
  6. CAPTCHA/risk engine — future extension.

Realm включает brute-force protection с temporary lockout и bounded wait. Permanent lockout для consumer phone login без recovery runbook нежелателен. Error messages не различают unknown phone/wrong code/locked account сверх безопасной UX причины. Retry-After/remaining time выдаётся только если не помогает enumeration.

IP берётся только из trusted proxy chain; Keycloak настроен доверять forwarded headers от root nginx.

10. Claims и token contract

Access token минимум:

Claim Значение
iss https://tohin.ru/auth/realms/han-chat
sub immutable Keycloak user id
aud включает han-chat-api
azp han-chat-frontend
exp, iat, nbf стандартные
sid session id, если поддерживается
auth_time время auth
acr/amr отражает phone OTP
phone_number canonical E.164
phone_number_verified true
scope only allowed scopes

preferred_username может совпадать с phone для compatibility, но канонический claim API — phone_number; module-01 допускает fallback только если E.164.

ID token предназначен client login state; API принимает access token, не ID token. Refresh token непрозрачен для приложения и хранится frontend secure storage.

PII minimization: full phone нужен API bootstrap по зафиксированному контракту, но не добавляется в service tokens/metrics/logs. Roles/groups выдаются только если используются authorization policy.

11. Signing keys, JWKS и rotation

  • asymmetric signing, RS256 MVP; none/HS algorithms запрещены;
  • active signing key + passive previous keys до истечения всех выпущенных tokens/grace;
  • keys генерируются/хранятся Keycloak, private material не в realm export/repo;
  • JWKS публичен через issuer;
  • rotation rehearsed; kid меняется, API controlled-refresh cache;
  • emergency compromise: disable key, revoke sessions, force re-login, alert/runbook;
  • backup/restore учитывает realm keys.

Rotation interval и HSM/keystore — ops TBD. Изменение algorithm требует совместного rollout API verifier.

12. Token и session lifecycle

Предлагаемые MVP значения, окончательно принять security/product review:

  • access token lifespan: 5 минут;
  • SSO session idle: 30 дней;
  • SSO session max: 90 дней;
  • refresh token следует session limits;
  • authorization code: 1 минута;
  • login action: 5 минут;
  • client session idle/max согласованы с SSO;
  • clock skew минимальный.

Refresh:

  • revoke refresh token on use / refresh token rotation включены;
  • max reuse 0 или минимально поддерживаемое значение;
  • frontend применяет single-flight, поэтому parallel refresh не требуется;
  • reuse старого refresh token → invalid_grant, возможная session revocation/security event;
  • offline tokens не выдаются.

Access token не хранится server-side и живёт до exp; критическая блокировка пользователя сопровождается logout/revocation/not-before policy.

13. Logout, revocation и browser cookies

Frontend вызывает OIDC end-session/logout с valid post-logout redirect, затем всегда очищает local tokens. Back-channel logout можно включить для clients, которые его поддержат; API JWT hot path не хранит browser session.

Cookies Keycloak:

  • Secure, HttpOnly;
  • SameSite согласно redirect/iframe requirements, по умолчанию Lax;
  • domain/path минимальны (/auth/host);
  • third-party cookie dependency не закладывается;
  • session fixation предотвращается Keycloak;
  • admin console cookies не расширяются на frontend origins.

Front-channel iframe checks не должны заставлять ослабить CSP всего сайта. Native logout использует system browser и app-link/custom scheme validation.

14. CORS, origins и redirects

  • exact Web Origins: https://tohin.ru;
  • no wildcard * with credentials;
  • native apps не получают произвольные web origins;
  • valid redirects exact/safely scoped;
  • redirect URI comparison не допускает open redirect;
  • post-logout redirects отдельно allow-listed;
  • nginx и Keycloak CORS не должны дублировать противоречащие headers;
  • token endpoint используется PKCE client без client secret;
  • admin endpoints не CORS-доступны приложению.

Любой новый environment имеет отдельный host/client config, а не production wildcard.

15. Reverse proxy и hostname

Ключевые настройки (точные CLI names проверяются по закреплённой версии):

KC_HTTP_ENABLED=true
KC_HTTP_PORT=8080
KC_PROXY_HEADERS=xforwarded
KC_HOSTNAME=https://tohin.ru/auth
KC_HTTP_RELATIVE_PATH=/auth
KC_HOSTNAME_STRICT=true
KC_HOSTNAME_STRICT_HTTPS=true

Если выбран другой поддержанный pattern (hostname без path + relative path), итоговые issuer/endpoints обязаны совпасть с KEYCLOAK_PUBLIC_URL.

Nginx передаёт trusted Host, X-Forwarded-Proto=https, X-Forwarded-Host, X-Forwarded-Port=443, real IP. Keycloak не доступен напрямую с host/public network, поэтому spoofed forwarded headers не принимаются извне.

Admin hostname/path рекомендуется ограничить ops network/VPN; публично нужны только realm/OIDC/login assets. Если разделить admin hostname невозможно MVP, admin console защищается network allow-list и сильным admin auth.

16. PostgreSQL keycloak

Используется:

KEYCLOAK_DB_URL=jdbc:postgresql://.../han_chat?...&currentSchema=keycloak
KC_DB_URL_PROPERTIES=currentSchema=keycloak

Role keycloak_user имеет доступ только к schema keycloak; нет доступа han_app, bitrix_*, message_safety. Connection только private VPC + TLS verify.

Pool:

  • bounded initial/min/max;
  • acquisition/query/connect timeout;
  • leak detection/metrics;
  • pool max определяется load test и managed PG limit;
  • application_name=keycloak.

Keycloak управляет своей стандартной schema migration. Custom provider tables имеют отдельную versioned migration strategy, совместимую с startup/rolling upgrade; DDL не выполняется бесконтрольно каждым replica.

Нельзя редактировать стандартные Keycloak tables вручную или Alembic-миграциями Python-сервисов.

17. Admin bootstrap и realm import

17.1. Bootstrap admin

  • KC_BOOTSTRAP_ADMIN_USERNAME/password или актуальный bootstrap mechanism только на первом запуске;
  • password генерируется strong secret, не коммитится и после bootstrap ротируется/удаляется из runtime env;
  • admin user не используется приложением;
  • отдельные named admin accounts/least privilege для ops;
  • MFA для admin обязательно до production, независимо от consumer phone flow;
  • admin events audit включён.

17.2. Realm import

Репозиторий:

keycloak/
  realm/han-chat-realm.json.template
  providers/han-phone-otp-provider.jar
  themes/han-phone/
  migrations/
  scripts/{render-realm,validate-realm,export-realm}.sh
  tests/
  Dockerfile
  docker-compose.yml

Export/template содержит realm/client/flow/scopes/policies, но не:

  • client/admin/provider secrets;
  • mock code;
  • private signing keys;
  • environment-specific production credentials.

Import автоматически допустим для clean local/test. Production changes применяются controlled declarative job/Admin API procedure с diff/backup, не --import-realm поверх живого realm без проверки. Drift detection сравнивает безопасный desired subset.

18. Settings и secrets

Канонические:

KEYCLOAK_PUBLIC_URL=https://tohin.ru/auth
KEYCLOAK_INTERNAL_URL=http://keycloak:8080
KEYCLOAK_REALM=han-chat
KEYCLOAK_AUDIENCE=han-chat-api
KEYCLOAK_DB_URL=jdbc:postgresql://...
KC_DB_URL_PROPERTIES=currentSchema=keycloak
KEYCLOAK_OTP_MOCK_ENABLED=true
KEYCLOAK_OTP_MOCK_CODE=<secret>
KEYCLOAK_SETTINGS_BRIDGE_TOKEN=<secret>
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317

Дополнительные Keycloak-standard env version-specific (KC_DB, hostname/proxy/health/metrics/pool) фиксируются в .env.example после выбора image. Секреты только root .env/secret mounts с минимальными permissions.

Product limits otp.phone.* не дублируются env. OTP TTL/max verify attempts — security technical config provider-а; их имена нужно добавить в arch-04 до реализации, например:

KEYCLOAK_OTP_TTL_SEC=300
KEYCLOAK_OTP_MAX_VERIFY_ATTEMPTS=5
KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC=300
KEYCLOAK_OTP_HMAC_KEY=<secret>

19. Health, readiness и startup

Keycloak management health endpoints включены. Compose проверяет liveness/startup; readiness требует:

  • server started;
  • DB reachable/schema migration complete;
  • realm/client/auth flow/provider loaded;
  • active signing key;
  • settings bridge last-known-good для OTP send;
  • mock enabled с valid secret либо реальный provider configured.

Стандартный Keycloak health сам не знает business provider state; custom provider readiness check/sidecar/synthetic internal check дополняет его. Public synthetic проверяет discovery/JWKS и authorization endpoint без отправки OTP.

DB/settings failure не должен приводить к выдаче tokens без OTP. OTEL/metrics outage не блокирует login.

20. Logging, metrics, tracing и audit

Logs

JSON/stdout:

  • service/version/environment, event/category;
  • request/trace id, realm/client, safe flow step;
  • result/error code, duration;
  • phone только HMAC/masked при необходимости.

Запрещены raw OTP/mock code, phone, access/refresh/code, cookies, Authorization, client/admin secret, password, form body, redirect query с code, DB URL.

Keycloak access log должен редактировать sensitive query. TRACE/DEBUG production выключены.

Events/audit

Включаются login/login_error, logout, refresh/revoke, user create/disable, phone verify/change, brute-force/OTP limit, admin config changes. Retention/consumer определяется ops/legal; event payload минимален.

Metrics

  • login/OTP send/verify success/failure/latency;
  • limit/lockout rejects;
  • settings cache age/refresh failures;
  • active sessions/token refresh/error;
  • DB pool/JVM/GC/HTTP;
  • JWKS/key age;
  • provider mode info (mock, later vendor), без phone labels.

Tracing

OTEL support зависит от версии; HTTP/provider/settings bridge spans добавляются instrumentation без secrets. Если native tracing недостаточно, сохраняются request/trace correlation headers. Наблюдаемость не меняет auth outcome.

21. Backup, restore и disaster recovery

  • managed PostgreSQL daily backup + PITR;
  • realm config export хранится versioned и secret-free;
  • signing key/private realm state входит в protected DB backup;
  • provider JAR/theme/image reproducible из repo/artifacts;
  • restore rehearsal в isolated environment;
  • после restore проверяются issuer, keys/JWKS, clients/flows, user/session consistency, provider tables;
  • RPO/RTO фиксируются ops до production.

При восстановлении в другой host нельзя случайно выдать production tokens с неверным issuer. DNS/TLS/hostname проверяются до открытия traffic. Backup encrypted/access-controlled; OTP expired rows очищаются по TTL.

22. Миграции и upgrades

Порядок:

  1. прочитать release notes и supported DB upgrade path;
  2. backup/PITR checkpoint;
  3. проверить provider SPI/API compatibility и пересобрать JAR;
  4. прогнать upgrade clone БД;
  5. contract/E2E login+refresh+logout;
  6. staged maintenance/rolling rollout только если версия поддерживает cluster compatibility;
  7. проверить schema migration, realm drift, JWKS;
  8. rollback приложения возможен только если DB schema backward-compatible; иначе restore/forward-fix runbook.

Нельзя пропускать major versions произвольно. Realm changes versioned отдельно. Custom provider migration имеет собственный version table/compatibility matrix.

23. Docker/runtime hardening

  • service keycloak, expose: 8080 и management port только internal;
  • networks public (только nginx access при необходимости), backend, observability;
  • без host ports;
  • non-root, read-only rootfs где совместимо, tmpfs для temp;
  • no-new-privileges/drop capabilities;
  • memory/CPU/JVM heap limits, graceful termination;
  • startup/readiness probes с достаточным initial period;
  • immutable provider/theme mounts/image;
  • no local persistent DB volume.

TLS client→nginx; Keycloak→managed PG TLS. Internal nginx→Keycloak HTTP допустим только на закрытой сети одной VM.

24. Failure semantics

Сбой Поведение
DB down not-ready; login/refresh fail; existing access tokens проверяются API до exp по cached JWKS
settings bridge down, cache valid send limits по last-known-good
settings bridge down, cache empty/stale new OTP send fail-closed
mock secret missing/invalid startup/not-ready; OTP не bypass
SMS mode без provider not-ready otp_provider_unconfigured
wrong OTP generic error, increment counter
too many sends/verifies temporary reject/lockout, safe UX
token signing key rotation old keys passive в JWKS grace
OTEL down auth работает, telemetry drop metric/local log
provider exception flow fail-closed, generic error/request id

Не должно быть fallback на password или «успешный OTP» при инфраструктурной ошибке.

25. Тестовая матрица

Unit provider

  • E.164 normalization across RU/international/Unicode;
  • invalid/impossible phone;
  • unique concurrent reservation;
  • mock constant-time compare/redaction;
  • challenge TTL/one-time/replay/concurrent verify;
  • send/verify limits and window boundaries;
  • settings cache/ETag/stale/fail-closed;
  • phone HMAC/counter cleanup;
  • provider SPI error mapping.

Realm/config contract

  • only standard code+PKCE S256;
  • password/direct/implicit/social disabled;
  • exact origins/redirect/logout URIs;
  • audience/claims/issuer;
  • token/session TTL and refresh rotation;
  • browser flow executions/required actions;
  • no secrets/private keys in realm export.

Integration

  • managed/test PostgreSQL schema/currentSchema/TLS;
  • restart preserves counters/challenges;
  • Keycloak upgrade/provider migration;
  • settings bridge token/path with module-01;
  • JWKS rotation and API validation;
  • disabled user/revocation/not-before;
  • brute-force lockout/recovery;
  • proxy hostname/path builds correct external URLs.

E2E

  • new phone → mock OTP → PKCE tokens → API bootstrap;
  • existing user login; valid refresh without OTP;
  • expired/revoked/rotated refresh → re-auth;
  • wrong/expired/replayed code;
  • max sends/min interval/max verifies;
  • concurrent tabs/refresh single-flight assumptions;
  • logout web/native;
  • DB/settings outage;
  • no phone/OTP/token in logs, URLs or metrics;
  • admin endpoint inaccessible publicly.

Security

  • redirect/open redirect, PKCE downgrade, state/nonce;
  • user enumeration/timing;
  • cookie flags/CSRF on auth forms;
  • forwarded header spoofing;
  • brute-force/IP/phone distributed attempts;
  • JWT alg/aud/iss/kid attacks;
  • secret scanning/image/SBOM/provider dependency review.

26. Definition of Done

  • Keycloak доступен за /auth, issuer/discovery/JWKS стабильны;
  • realm/client topology и PKCE S256 зафиксированы declaratively;
  • только phone OTP; password/implicit/direct/social отключены;
  • phone canonical E.164 и storage-level unique;
  • claims соответствуют module-01 (sub, phone_number, audience);
  • mock secret only env, не логируется/не отдаётся;
  • OTP challenges/counters durable в Keycloak schema;
  • product limits читаются только через canonical settings bridge/token;
  • brute-force, TTL, verify attempts и enumeration protection работают;
  • refresh rotation/reuse detection/logout/revocation покрыты;
  • proxy/redirect/origin/CORS/cookies/TLS boundaries проверены;
  • DB role/schema/backup/restore/upgrade runbooks готовы;
  • health/metrics/logging/tracing не раскрывают secrets/PII;
  • container hardening/root Compose без published port;
  • test matrix зелёная;
  • реальный SMS явно остаётся extension point, не скрытой заглушкой.

27. Решения, допущения и TBD

Решения:

  • K1: realm han-chat, public client han-chat-frontend, audience han-chat-api.
  • K2: Authorization Code + PKCE S256; остальные user grants выключены.
  • K3: canonical identity/claim — E.164 phone_number; sub immutable.
  • K4: OTP authenticator/provider SPI; mock code только secret env.
  • K5: counters/challenges в provider-owned PostgreSQL schema keycloak, не API Redis.
  • K6: product limits только /internal/settings/v1/otp + KEYCLOAK_SETTINGS_BRIDGE_TOKEN.
  • K7: refresh rotation/revoke-on-use; frontend single-flight.
  • K8: real SMS provider — extension point/TBD.

Допущения:

  • A1: единый public host tohin.ru и relative path /auth.
  • A2: Keycloak version поддерживает нужные hostname/proxy/health options; точные names pin после выбора image.
  • A3: product допускает mock OTP в первой production-like среде как временный риск.
  • A4: телефон в access token необходим API bootstrap и защищён TLS/short token TTL.

TBD:

  • K-TBD1: выбрать/pin Keycloak version и проверить custom SPI compatibility.
  • K-TBD2: окончательные redirect URI для Expo iOS/Android и universal/app links.
  • K-TBD3: финальные token/session TTL и brute-force thresholds после security review.
  • K-TBD4: exact schema/migration mechanism provider tables без вмешательства в standard schema.
  • K-TBD5: admin MFA/ops access topology и отдельный admin hostname.
  • K-TBD6: signing-key rotation interval/HSM и emergency revocation.
  • K-TBD7: RPO/RTO/event retention/legal deletion.
  • K-TBD8: SMS vendor, credentials, templates, sender, delivery receipts and failover.
  • K-TBD9: CAPTCHA/risk scoring после mock.
  • K-TBD10: добавить proposed OTP technical env в arch-04 до реализации.