Appearance
Направление: Web-приложение
Authorization Code Flow с PKCE — стандартный вариант для приложений с браузером на стороне пользователя: классические веб-сайты, SPA, мобильные приложения с системным браузером/WebView.
Предполагается, что клиент уже создан (02-client.md) и вы знаете client_id, client_secret и хотя бы один зарегистрированный redirect_uri.
Шаг 0 — сгенерировать PKCE-пару
На каждый запрос входа генерируется новая пара: случайная строка code_verifier и её хэш code_challenge.
js
const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(sha256(codeVerifier));code_verifier храните на своей стороне (сессия/cookie) до шага 2 — на клиент он не отправляется.
Шаг 1 — редирект пользователя на /authorize
GET /authorize
?response_type=code
&client_id=<ваш client_id>
&redirect_uri=<ваш redirect_uri, должен точно совпадать с зарегистрированным>
&scope=openid profile email offline_access
&state=<случайная строка против CSRF>
&code_challenge=<code_challenge>
&code_challenge_method=S256| Параметр | Обязателен | Заметка |
|---|---|---|
response_type | ✓ | Всегда code |
redirect_uri | ✓ | Точное совпадение с одним из значений в настройках клиента |
scope | ✓ | Минимум openid. Добавьте offline_access, если нужен refresh_token |
state | рекомендован | Проверяйте на своей стороне при возврате |
nonce | — | Если проверяете привязку ID Token к конкретному запросу |
prompt=consent | — | Принудительно показать экран согласия, даже если пользователь уже давал его раньше |
Если у клиента задана организация с политикой входа (mfaRequired, allowedEmailDomains и т.д.) — все проверки выполняются здесь автоматически, до того как пользователь попадёт на ваш redirect_uri. Что при этом видит пользователь — appendix-login-policies.md.
Результат: редирект на redirect_uri?code=<code>&state=<state>, либо на redirect_uri?error=... при отказе (см. таблицу ошибок в 06-operations.md).
Pushed Authorization Requests (опционально)
Если не хотите передавать параметры авторизации через query string браузера (логи прокси, history, Referer), отправьте их напрямую на сервер до редиректа:
POST /par (те же параметры, что в Шаге 1, плюс client_secret)В ответ — одноразовый request_uri, который живёт expires_in секунд (обычно 90). Дальше редирект делается с ним вместо полного набора параметров:
GET /authorize?client_id=<client_id>&request_uri=<request_uri>Не обязателен — прямой способ (Шаг 1 целиком) продолжает работать как раньше.
Шаг 2 — обмен кода на токены
Код действителен 10 минут, одноразовый.
bash
curl -X POST http://localhost:3002/token \
-d "grant_type=authorization_code" \
-d "code=<code из шага 1>" \
-d "redirect_uri=<тот же redirect_uri, что в шаге 1>" \
-d "client_id=<client_id>" \
-d "client_secret=<client_secret>" \
-d "code_verifier=<code_verifier из шага 0>"json
{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "<JWT>",
"refresh_token": "<opaque>",
"scope": "openid profile email offline_access"
}refresh_token приходит только если запрашивался scope offline_access. Дальше — обычная работа с сессией на своей стороне: сохраните access_token/refresh_token, свяжите с локальной сессией пользователя.
sid в ID Token. Сохраните его — это идентификатор серверной сессии SSO. Он понадобится, чтобы сматчить входящий logout_token (back-channel logout ниже) со своей локальной сессией.
Шаг 3 — обновление токена
bash
curl -X POST http://localhost:3002/token \
-d "grant_type=refresh_token" \
-d "refresh_token=<refresh_token>" \
-d "client_id=<client_id>" \
-d "client_secret=<client_secret>"Возвращает новую пару access_token/refresh_token (rotation) — старый refresh_token немедленно отзывается. Не переиспользуйте отозванный refresh token — повторный вызов с ним вернёт invalid_grant.
Экран согласия
При первом входе (и повторно, если запрошен prompt=consent или добавлен новый scope) пользователь видит экран согласия с названием и логотипом вашего приложения (см. брендирование в 02-client.md). Отказ пользователя — редирект с error=access_denied на ваш redirect_uri. Список выданных пользователем согласий и их отзыв — на его стороне, /settings/authorized-apps.
Logout
RP-Initiated Logout
Приложение само инициирует выход, отправив пользователя на:
GET /logout?id_token_hint=<id_token>&post_logout_redirect_uri=<uri>&state=<state>id_token_hint— ранее полученный ID Token (подпись проверяется, истечение допускается).post_logout_redirect_uri— обязан быть заранее внесён вpostLogoutRedirectUrisклиента, иначе игнорируется.state— вернётся как query-параметр при редиректе обратно.
Back-Channel Logout
Если у клиента задан backchannelLogoutUri, при завершении сессии (в том числе через /account/sessions со стороны пользователя) SSO отправит server-to-server POST:
POST <backchannelLogoutUri>
Content-Type: application/x-www-form-urlencoded
logout_token=<подписанный RS256 JWT>json
{
"iss": "http://localhost:3002",
"sub": "<userId>",
"aud": "<client_id>",
"sid": "<sessionId>",
"events": { "http://schemas.openid.net/event/backchannel-logout": {} }
}На своей стороне: проверьте подпись через /jwks, iss/aud, наличие events, отсутствие nonce, и завершите локальную сессию с этим sid — тем самым, который вы сохранили на Шаге 2. Таймаут доставки — 3 секунды; недоступность вашего эндпоинта не блокирует выход пользователя.
Front-Channel Logout
Резервный канал для тех, кто не может принимать server-to-server вызовы. Если задан frontchannelLogoutUri, он подгружается скрытым <iframe sandbox> на странице /logout. Работает только для текущей сессии — при удалённом завершении чужой сессии (см. ниже) front-channel технически невозможен, браузера той сессии уже нет.
«Выйти со всех устройств»
Пользователь может завершить сразу все свои сессии через /account/sessions → «Выйти со всех устройств» (POST /account/logout-everywhere). Для вашего приложения это неотличимо от нескольких обычных back-channel-уведомлений подряд — по одному на каждую активную сессию пользователя.
Дальше
Справочник кодов ошибок, лимиты запросов и план на компрометацию client_secret — 06-operations.md.