Files

8.8 KiB
Raw Permalink Blame History

SSO / Auth — исследование и контекст

Дата: 2026-05-30
Актуально для ветки sonnet


Исходные данные от команды

Ссылки (получены 2026-05-30)

Что URL Примечание
Пример SSO на ColdFusion https://gitea.services.ngcloud.ru/smishchuk/payg-report.git Написан «на коленке», но рабочий
Конфигурация сервиса в Deck https://deck.ngcloud.ru/services/instance/detail/523413d2-1ffd-457b-b0e3-0a8185fe9358 Там видно как кладутся env-переменные

Файлы из payg-report сохранены локально

research/payg-sso/
  Application.cfc   — основной SSO flow (редирект, обмен code→token, refresh, logout)
  lib_oauth2.cfc    — Authorization Code Flow клиент
  lib_jwt.cfc       — валидация JWT RS256

Keycloak — эндпоинты

Realm: cloud
Base: https://keycloak.nubes.ru/realms/cloud

Назначение URL
Авторизация (браузер → KK) https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth
Обмен code → token https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token
Logout https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/logout
JWKS (публичные ключи) https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs
OpenID конфигурация https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration

Реальная схема аутентификации

Браузер
  │
  │ 1. GET /  (без токена)
  ▼
наше приложение
  │
  │ 2. redirect → KK /auth?client_id=...&redirect_uri=...&scope=openid
  ▼
Keycloak (keycloak.nubes.ru, realm=cloud)
  │
  │ 3. Пользователь логинится
  │ 4. KK redirect → наш /callback?code=...
  ▼
наше приложение
  │
  │ 5. POST /token (code + client_secret) → access_token + refresh_token
  │ 6. Валидация JWT через JWKS (certs endpoint)
  │ 7. Извлечение claims из JWT → req.user
  ▼
  Работаем: req.user.ClientID = "WZ01325" и тд

Важно из примера payg-report:

  • Тип flow: Authorization Code Flow (не Implicit, не PKCE — обычный)
  • client_secret передаётся при обмене code→token (confidential client)
  • Сертификат берётся с JWKS endpoint при старте, кэшируется в application scope
  • При истечении части времени жизни — автоматический refresh через refresh_token

JWT — структура токена

JWT выпускает auth-api (не Keycloak напрямую).
Алгоритм: RS256.
JWKS для валидации: из auth-api (уточнить URL у DevOps).

⚠️ В payg-report используют keycloak.nubes.ru/certs, но наш портал deck.ngcloud.ru
идёт через auth-api.ngcloud.ru — нужно уточнить чей именно JWT будет у нас.

Claims в JWT (из реального токена, secrets.txt)

Claim Тип Пример Назначение
iss string "auth-api" Издатель
sub string "0199e325-1cdf-..." UUID пользователя
exp number 1795627443 Срок действия
iat number 1780075443 Выпущен
ClientID string "WZ01325" WZ-номер компании ← ключевой claim
company_id string (UUID) "3e64aac6-dcfc-..." UUID компании
company_name string "Тест" Название компании
email string "tazet@narod.ru" Email (для аудита)
login string "tazet@narod.ru" Логин
firstname string "Наиль" Имя
lastname string "Тазетдинов" Фамилия
token_type string "tech" Тип токена

Пример декодированного payload (из secrets.txt — не секрет, payload JWT не зашифрован):

{
  "iss": "auth-api",
  "sub": "0199e325-1cdf-7cda-9319-e5302a85e291",
  "ClientID": "WZ01325",
  "company_id": "3e64aac6-dcfc-4082-88dc-da19c86555a5",
  "company_name": "Тест",
  "email": "tazet@narod.ru",
  "login": "tazet@narod.ru",
  "token_type": "tech"
}

Конфигурация сервиса (env-переменные)

Из примера payg-report и Deck — конфиги кладутся в переменные окружения:

# Нужно получить от DevOps:
KC_CLIENT_ID=white.nodejsk8s.dev.nubes.ru   # имя нашего клиента в KK (уточнить)
KC_CLIENT_SECRET=<секрет>                   # из Deck / у DevOps
JWKS_URL=https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs
# ИЛИ если через auth-api:
# JWKS_URL=https://auth-api.ngcloud.ru/.well-known/jwks.json

# В payg-report переменная называется:
IDP_CLIENT_SECRET=<секрет>

Что сейчас в нашем коде

Файл: src/auth.js

Режим Условие Поведение
DEV_MODE DEV_MODE=true и не production Авторизация пропущена, юзер захардкожен из MOCK_USERS
JWKS_URL задан JWKS_URL != '' Валидирует Bearer-токен из заголовка через JWKS
Мок RS256 Ничего из выше Генерирует собственную RSA-пару, выдаёт и валидирует JWT сам

Чего не хватает для продакшена:

  • Authorization Code Flow (редирект на KK и обратно)
  • Обмен codeaccess_token через /token
  • Session/cookie для хранения токена между запросами
  • Refresh токена

Пример SSO flow из payg-report (ключевые строки)

// Настройка (из окружения):
const client_id     = process.env.IDP_CLIENT_SECRET; // их название
const auth_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth';
const token_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token';
const certs_url     = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs';

// 1. Нет сессии → redirect на KK:
location(idp.buildRedirectToAuthURL({scope: 'openid profile email', state: guid}));

// 2. KK вернул ?code=... → меняем на токен:
const resp = idp.makeAccessTokenRequest(url.code);

// 3. Парсим JWT из ответа:
const token_data = jwt.decode(resp.access_token, idpCertificate, 'RS256');
session.auth.wz = token_data.ClientID;  // WZ-номер компании
session.auth.login = token_data.preferred_username;

// 4. Refresh когда прошло > 2/3 времени жизни (только на GET):
if (timeSinceIat > refresh_expires_in / 1.5 && method === 'GET') {
    const resp = idp.refreshAccessTokenRequest(session.auth.refresh_token);
    // ... обновляем сессию
}

Открытые вопросы (нужен ответ от DevOps/KK-команды)

# Вопрос Влияет на
1 Какой client_id выдан нашему сервису в KK realm cloud? Конфиг
2 Где взять client_secret? Конфиг
3 Токен от KK или от auth-api? Соответственно — какой JWKS_URL? src/auth.js
4 Claim для admin-роли: чек-бокс — это realm_roles, resource_access, attribute? src/auth.js requireAdmin
5 ClientID в токене — строка или массив (multi-company)? src/queries.js, UI
6 /export — нужна ли авторизация или только сетевое ограничение по IP? src/routes/export.js

API Gateway (для справки)

Из docs/auth-architecture.md:

  • Gateway: lk-api-gateway.ngcloud.ru
  • JWT из localStorage портала: authApiTokens.access_token
  • DDOS-Guard cookies обязательны при curl-тестировании: __ddg1_, __ddg8_, __ddg9_, __ddg10_
# Пример рабочего curl (2026-05-29):
curl -H "Authorization: Bearer <JWT>" \
     -H "Origin: https://deck.ngcloud.ru" \
     -H "Cookie: __ddg1_=...; __ddg8_=...; __ddg9_=...; __ddg10_=..." \
     "https://lk-api-gateway.ngcloud.ru/api/v1/..."