Бюро Цифровых Технологий

База знаний

Документация «Вираж»

База знаний по продуктам и API экосистемы «Вираж»: модули, методы, вебхуки — с примерами.

Обновлено: 05.10.2026

Единый вход на сайт клуба

Вход в личный кабинет авторизует болельщика и на сайте клуба. Логаут происходит на всех сайтах одновременно. Сеанс хранится на сервере платформы, в браузер уходит только идентификатор в cookie домена (.example.com), доступной всем поддоменам. CMS сайта значения не имеет: нужны два запроса, introspect и revoke.

Порядок вызовов

  • Кабинет: POST /user/login с session: true. Платформа отвечает двумя cookie.
  • Кабинет: запросы к API с credentials: "include", без заголовка Authorization.
  • Сайт: читает cookie virazh_sso, вызывает POST /sso/introspect, получает профиль, ставит свою сессию.
  • Выход: кабинет вызывает POST /lk/auth/logout, сайт вызывает POST /sso/revoke.
Схема
браузер                 кабинет            платформа              сайт клуба
  |   логин/пароль  ----->   |                   |                       |
  |                          |-- POST /user/login ->                     |
  |<---- Set-Cookie: virazh_lk (crm.example.com) -|                      |
  |<---- Set-Cookie: virazh_sso (.example.com) ---|                      |
  |   открывает сайт  --------------------------------------------------> |
  |   (доменная cookie уходит сама)               |<- POST /sso/introspect
  |                          |                   |--- профиль ---------->|
  |<------------------ Set-Cookie: сессия сайта (.example.com) ---------- |

Методы

МетодПутьВызываетАвторизацияЛимит
POST/user/loginкабинетнет10/мин
GET/sso/whoamiкабинетcookie или Bearer60/мин
POST/sso/introspectсервер сайтаX-Sso-Secret1200/мин
POST/sso/revokeсервер сайтаX-Sso-Secret60/мин
POST/sso/ticketкабинетBearer20/мин
POST/sso/redeemсервер сайтаX-Sso-Secret60/мин
POST/lk/auth/logoutкабинетcookie или Bearerнет

POST /user/login

Вход болельщика. С session: true создаёт сеанс и ставит cookie.

ПараметрТипОбязателенОписание
emailstringнетEmail, либо он, либо `phone`
phonestringнетТелефон в формате `+7…`
passwordstringдаПароль
sessionbooleanнетСоздать сеанс и поставить cookie
rememberbooleanнетДлинный сеанс, 30 суток
Ответ 200
Set-Cookie: virazh_lk=…; Path=/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: virazh_sso=…; Domain=.example.com; Path=/; Secure; HttpOnly; SameSite=Lax

{
  "data": {
    "user": { "id": 18422, "name": "Иван", "lastname": "Иванов" },
    "token": "18422|…",
    "session": true
  }
}

session: false означает, что на платформе не задан SSO_COOKIE_DOMAIN. Токен в ответе есть всегда, по нему работают мобильное приложение и запасной путь через тикет.

GET /sso/whoami

Проверка сеанса кабинетом: 200 с профилем, если cookie принята, 401 если нет.

Ответ 200
{
  "data": {
    "session": true,
    "user": { … тот же профиль, что отдаёт introspect … }
  }
}

POST /sso/introspect

Профиль по доменной cookie. Вызывает сервер сайта, ключ в заголовке.

Запрос
curl -X POST https://crm.example.com/api/v1/sso/introspect \
  -H 'X-Sso-Secret: <ключ сайта>' \
  -H 'Content-Type: application/json' \
  -d '{"id":"<значение cookie virazh_sso>"}'
Ответ 200
{
  "data": {
    "remember": true,
    "expires_at": 1793000000,
    "user": {
      "id": 18422,
      "email": "fan@example.com",
      "phone": "+79990001122",
      "name": "Иван",
      "lastname": "Иванов",
      "patronymic": null,
      "display_name": "Иван Иванов",
      "avatar": "https://crm.example.com/storage/avatars/18422.webp",
      "points": 2400
    }
  }
}
СтатусЗначение
200Сеанс жив, профиль отдан
401Ключ не совпал
410Сеанс истёк, погашен, аккаунт отключён, либо значение не похоже на идентификатор
503На платформе не задан `SSO_SHARED_SECRET`

POST /sso/revoke

Гасит сеанс целиком. Вызывается сайтом при выходе, после этого кабинет получает 401.

Запрос
curl -X POST https://crm.example.com/api/v1/sso/revoke \
  -H 'X-Sso-Secret: <ключ сайта>' \
  -H 'Content-Type: application/json' \
  -d '{"id":"<значение cookie virazh_sso>"}'
# → { "data": { "revoked": true } }

POST /sso/ticket и /sso/redeem

Запасной путь для сайта на другом домене второго уровня, куда доменная cookie не доходит. Тикет живёт 120 секунд и гаснет при первом обмене, повторный даёт 410. Кабинет передаёт его фоновым запросом или переходом на https://сайт/?virazh_sso=<тикет>.

Выдача и обмен
curl -X POST https://crm.example.com/api/v1/sso/ticket \
  -H 'Authorization: Bearer <токен болельщика>' \
  -H 'Content-Type: application/json' \
  -d '{"audience":"site","remember":true}'
# → { "data": { "ticket": "9f1c…64 hex…a7", "expires_in": 120 } }

curl -X POST https://crm.example.com/api/v1/sso/redeem \
  -H 'X-Sso-Secret: <ключ сайта>' \
  -H 'Content-Type: application/json' \
  -d '{"ticket":"9f1c…a7"}'
# → профиль, как у introspect, плюс audience и remember

Cookie

CookieДоменЧитаетНазначение
virazh_lkдомен платформыплатформаАвторизация запросов кабинета к API
virazh_sso.example.comсайт, платформаИдентификатор сеанса для интроспекции

Обе cookie помечены HttpOnly, Secure, SameSite=Lax. virazh_sso видят все поддомены, поэтому доступа к API она не даёт: по ней можно только получить профиль, предъявив ключ. Без «Запомнить меня» обе cookie сеансовые, срок держит серверная запись: SSO_SESSION_LIFETIME, 120 минут, продлевается при обращениях кабинета и сайта.

Ключ сайта: заголовок X-Sso-Secret, значение совпадает с SSO_SHARED_SECRET на платформе. Выпускается командой openssl rand -hex 32, в браузер не передаётся.

Переменные платформы

ПеременнаяПо умолчаниюНазначение
SSO_SHARED_SECRETпустоКлюч сайта. Пусто: `introspect` и `redeem` отдают 503
SSO_COOKIE_DOMAINпустоДомен cookie и выключатель схемы. Пусто: только запасной путь
SSO_SESSION_LIFETIME120Минут, обычный сеанс
SSO_SESSION_REMEMBER_LIFETIME43200Минут, сеанс с «Запомнить меня»
SSO_ALLOWED_ORIGINSпустоOrigin, которым разрешены изменяющие запросы по cookie. Пусто: берётся `CORS_ALLOWED_ORIGINS`
SSO_COOKIE_SECUREtrueФлаг `Secure` у cookie
SSO_TICKET_TTL120Секунд, срок жизни тикета
SSO_API_COOKIEvirazh_lkИмя cookie кабинета
SSO_IDENTITY_COOKIEvirazh_ssoИмя доменной cookie

Подключение сайта

На запросе страницы: прочитать virazh_sso, вызвать introspect, авторизовать пользователя. При выходе: вызвать revoke. Пользователь сайта ищется по идентификатору болельщика, затем по email и телефону. Ответ интроспекции кешируется на 2–5 минут по хэшу cookie. Статус 410 означает выход, таймаут и 429 состояние сессии не меняют. Cookie сессии сайта ставится на домен .example.com.

Профиль по cookie
function virazhSsoProfile(): array|string|null
{
    $id = $_COOKIE['virazh_sso'] ?? '';
    if (preg_match('/^[a-f0-9]{64}$/', $id) !== 1) {
        return null;      // cookie нет: гость
    }

    $ch = curl_init('https://crm.example.com/api/v1/sso/introspect');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 8,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Sso-Secret: ' . SSO_KEY],
        CURLOPT_POSTFIELDS => json_encode(['id' => $id]),
    ]);
    $body = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code === 410) {
        return null;      // сеанса нет: разлогинить
    }
    if ($code !== 200) {
        return 'unknown'; // таймаут, 429, 5xx: сессию не трогать
    }

    return json_decode($body, true)['data']['user'];
}

Домен cookie сайта задаётся аргументом domain у setcookie(): .example.com.

Сторона кабинета

Вход и запросы
const res = await fetch(API + '/user/login', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password, session: true, remember }),
}).then((r) => r.json());

// cookie принята браузером?
const cookieWorks = await fetch(API + '/sso/whoami', {
  credentials: 'include',
}).then((r) => r.ok);

await fetch(API + '/lk/dashboard', { credentials: 'include' });

cookieWorks: false означает запасной путь: токен из ответа плюс тикет. Проверку ограничить тремя секундами. Изменяющие запросы по cookie принимаются только с Origin из SSO_ALLOWED_ORIGINS, иначе 401.

Диагностика

СимптомПричина
`session: false` при входеНе задан `SSO_COOKIE_DOMAIN`
Сайт считает гостемСтраничный кеш отдаёт страницу гостя
Сайт не видит cookieКабинет и сайт на разных доменах второго уровня
Гость на соседнем поддоменеСессия сайта поставлена на хост
`POST` даёт 401, `GET` работаетOrigin кабинета не в `SSO_ALLOWED_ORIGINS`, отказ пишется в лог
`401` на интроспекцииКлючи на сайте и платформе разошлись
`410` на интроспекцииСеанс погашен или истёк, сайту следует разлогинить

Ограничения

  • Направление одно: вход на сайте сеанс в кабинете не создаёт.
  • Вход по SMS-коду доменной сессии не создаёт, только токен.
  • Аккаунты с правами редактирования через единый вход не авторизуются.
  • Смена ключа нужна одновременно на платформе и на сайте, в промежутке интроспекция отдаёт 401.
  • Пароль пользователю, созданному единым входом, ставится случайный.