Skip to content

Направление: 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_secret06-operations.md.