Обновлено: 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 или Bearer | 60/мин |
POST | /sso/introspect | сервер сайта | X-Sso-Secret | 1200/мин |
POST | /sso/revoke | сервер сайта | X-Sso-Secret | 60/мин |
POST | /sso/ticket | кабинет | Bearer | 20/мин |
POST | /sso/redeem | сервер сайта | X-Sso-Secret | 60/мин |
POST | /lk/auth/logout | кабинет | cookie или Bearer | нет |
POST /user/login
Вход болельщика. С session: true создаёт сеанс и ставит cookie.
| Параметр | Тип | Обязателен | Описание |
|---|---|---|---|
email | string | нет | Email, либо он, либо `phone` |
phone | string | нет | Телефон в формате `+7…` |
password | string | да | Пароль |
session | boolean | нет | Создать сеанс и поставить cookie |
remember | boolean | нет | Длинный сеанс, 30 суток |
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 если нет.
{
"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>"}'{
"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 и rememberCookie
| 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_LIFETIME | 120 | Минут, обычный сеанс |
SSO_SESSION_REMEMBER_LIFETIME | 43200 | Минут, сеанс с «Запомнить меня» |
SSO_ALLOWED_ORIGINS | пусто | Origin, которым разрешены изменяющие запросы по cookie. Пусто: берётся `CORS_ALLOWED_ORIGINS` |
SSO_COOKIE_SECURE | true | Флаг `Secure` у cookie |
SSO_TICKET_TTL | 120 | Секунд, срок жизни тикета |
SSO_API_COOKIE | virazh_lk | Имя cookie кабинета |
SSO_IDENTITY_COOKIE | virazh_sso | Имя доменной cookie |
Подключение сайта
На запросе страницы: прочитать virazh_sso, вызвать introspect, авторизовать пользователя. При выходе: вызвать revoke. Пользователь сайта ищется по идентификатору болельщика, затем по email и телефону. Ответ интроспекции кешируется на 2–5 минут по хэшу cookie. Статус 410 означает выход, таймаут и 429 состояние сессии не меняют. Cookie сессии сайта ставится на домен .example.com.
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.
Плагин «Вираж: единый вход» делает оба запроса сам. Кеш профиля 5 минут, новый пользователь создаётся с ролью подписчика, выход с сайта вызывает revoke.
- Плагины → Добавить новый → Загрузить плагин →
virazh-sso.zip→ Активировать. - В
wp-config.phpдоrequire_once ABSPATH . 'wp-settings.php';:define( 'COOKIE_DOMAIN', '.example.com' ); - Настройки → «Вираж: единый вход»: адрес API, ключ сайта, имя cookie.
- Проверка:
GET /wp-json/virazh-sso/v1/statusотдаётlogged_in,crm_id,identity_seen,cookie_domain.
| Настройка | Значение |
|---|---|
Адрес API CRM | https://crm.example.com/api/v1 |
Ключ сайта | значение `SSO_SHARED_SECRET` |
Доменная сессия | включена |
Имя cookie | virazh_sso |
Роль новых пользователей | Подписчик |
Создавать пользователя | включено |
Вход привилегированных | выключено |
Метаполя: virazh_crm_id, virazh_phone, virazh_points, virazh_avatar, virazh_sso_last_login. Хуки: virazh_sso_logged_in, virazh_sso_user_created.
Готового модуля нет. Обработчик события в /local/php_interface/init.php, функция virazhSsoProfile() из вкладки «Свой бэкенд», идентификатор болельщика в UF_VIRAZH_ID.
BitrixMainEventManager::getInstance()
->addEventHandler('main', 'OnPageStart', 'virazhSsoSync');
function virazhSsoSync(): void
{
global $USER;
$profile = virazhSsoProfile();
if ($profile === 'unknown') {
return;
}
if ($profile === null) {
if ($USER->IsAuthorized() && !empty($_SESSION['VIRAZH_SSO'])) {
$USER->Logout();
}
return;
}
if ((int) ($_SESSION['VIRAZH_SSO'] ?? 0) === (int) $profile['id']) {
return;
}
$userId = virazhSsoFindOrCreateUser($profile); // UF_VIRAZH_ID, email, телефон
$USER->Authorize($userId);
$_SESSION['VIRAZH_SSO'] = (int) $profile['id'];
}
BitrixMainEventManager::getInstance()
->addEventHandler('main', 'OnBeforeUserLogout', 'virazhSsoRevoke');Авторизация на поддоменах включается в настройках главного модуля, вкладка «Авторизация», маска домена .example.com. Домен отдельной cookie в D7: Bitrix\Main\Web\Cookie::setDomain(). Композитный кеш и кеш страниц обязаны пропускать запросы с cookie авторизации.
Сторона кабинета
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. - Пароль пользователю, созданному единым входом, ставится случайный.
