Поддерживаем AI: MCP-сервер Авториза и готовые инструкции для ИИ-чата

Authorization Code Flow и PKCE

Authorization Code Flow — это последовательность, с помощью которой приложение получает токены после входа пользователя. Вместо того чтобы сразу возвращать токен в браузер, сервер авторизации сначала возвращает короткоживущий одноразовый код. Приложение затем обменивает этот код на токены.

PKCE — дополнительная защита этого обмена. Она доказывает серверу, что код обменивает именно то приложение, которое начало вход, а не тот, кто случайно перехватил код.

Коротко:

пользователь входит
    -> приложение получает временный code
    -> приложение доказывает право на этот code
    -> сервер выдаёт токены

Что означают названия

Authorization Code

Authorization Code — временный код авторизации. Это не Access Token и не подтверждение готовой сессии. Код нужен только для одного действия: обмена на токены через token endpoint.

Код возвращается на Redirect URI приложения. Если код попадёт в чужие руки, он всё равно не должен быть достаточным для получения токенов. Именно эту задачу дополнительно решает PKCE.

Flow

Flow означает последовательность сообщений между приложением, браузером пользователя и сервером авторизации. Вход не является одним HTTP-запросом: пользователь должен пройти аутентификацию, приложение должно получить callback, а затем завершить обмен кода.

PKCE

PKCE расшифровывается как Proof Key for Code Exchange, то есть «ключ-доказательство для обмена кода». В начале flow приложение создаёт секретную случайную строку code_verifier. Затем из неё вычисляется code_challenge, который можно передать серверу:

code_verifier   = случайная строка, хранится у приложения
code_challenge  = BASE64URL(SHA256(code_verifier))

На первом шаге сервер получает только code_challenge. На втором шаге приложение возвращает исходный code_verifier. Сервер сам вычисляет challenge ещё раз и сравнивает результаты.

Важно: code_verifier не должен передаваться в первом authorization request. Если отправить его сразу, проверка потеряет смысл.

Как проходит вход

Рассмотрим сценарий с браузерным приложением.

  1. Пользователь нажимает кнопку «Войти».
  2. Приложение создаёт code_verifier, state и nonce.
  3. Приложение вычисляет code_challenge из verifier.
  4. Приложение перенаправляет браузер на authorization endpoint.
  5. Пользователь входит и, если нужно, подтверждает запрошенные scopes.
  6. Сервер возвращает браузер на зарегистрированный Redirect URI с code и state.
  7. Приложение проверяет, что полученный state совпадает с сохранённым.
  8. Приложение отправляет code и исходный code_verifier на token endpoint.
  9. Сервер проверяет code, Redirect URI, client и соответствие verifier.
  10. После успешной проверки сервер возвращает токены.

В Authorization Code Flow пароль пользователя не передаётся приложению. Пароль вводится на стороне authorization server, а приложение получает только результат согласованного протокола.

Стандарт

На уровне протокола это договор между клиентом и authorization server:

  • клиент заранее сообщает challenge, но не раскрывает исходный verifier;
  • authorization server связывает выданный code с этим challenge;
  • при обмене клиент предъявляет verifier;
  • сервер выдаёт токены только после успешной проверки связи между двумя значениями.

Authorization Code Flow отвечает за разделение авторизации и выдачи токенов на два связанных этапа. PKCE добавляет к этому обмену доказательство владения verifier. Поэтому code можно передать через браузерный callback, не превращая сам callback в канал передачи готовых токенов.

┌──────────┐                         ┌──────────────────────┐
│ Клиент   │                         │ Authorization Server │
└────┬─────┘                         └──────────┬───────────┘
     │                                          │
     │ code_verifier                            │
     │ code_challenge = S256(code_verifier)     │
     │                                          │
     │ 1. Запрос авторизации                    │
     │ ────────────────────────────────────────>│
     │                                          │
     │ 2. Вход пользователя                     │
     │<─────────────────────────────────────────│
     │    authorization code                    │
     │                                          │
     │ 3. code + code_verifier                  │
     │ ────────────────────────────────────────>│
     │                                          │
     │     сервер проверяет:                    │
     │     S256(code_verifier) == code_challenge│
     │                                          │
     │ 4. ID Token + Access Token               │
     │<─────────────────────────────────────────│

code_challenge передаётся в первом запросе, а исходный code_verifier остаётся у клиента до обмена кода. Поэтому одного перехваченного authorization code недостаточно: без verifier сервер не должен выдавать токены.

Представьте, что authorization code — это номерок, который выдают на входе, а code_verifier — секретная часть квитанции, оставшаяся у настоящего клиента. Перехватчик может увидеть номерок, но не может завершить обмен без второй части.

Практика

Public client должен использовать PKCE, потому что его код выполняется на устройстве или в браузере. client_secret нельзя считать секретом, если он попадает в frontend bundle.

Зачем flow разделён на два шага

Authorization code короткоживущий и предназначен только для однократного обмена. Если его перехватят, злоумышленнику дополнительно понадобится code_verifier, который остаётся у исходного клиента. Поэтому code не заменяет токен и не должен использоваться приложением как доказательство успешной сессии.

У этого flow есть три разные проверки:

  • state связывает callback с запросом, который начал именно этот клиент;
  • nonce связывает ID Token с конкретной попыткой аутентификации;
  • PKCE связывает authorization code с клиентом, который создал verifier.

Эти значения решают разные задачи и не являются взаимозаменяемыми.

Public и Confidential

Для Public client секрет хранить негде: пользователь может посмотреть JavaScript и сетевые запросы. Поэтому приложение использует client_id и PKCE. Confidential client выполняет обмен на backend и дополнительно подтверждает себя client_secret.

Выбор типа подключения должен следовать архитектуре, а не названию фреймворка. Next.js с серверным callback может быть Confidential, а Next.js SPA-сценарий — Public. Критерий один: где реально выполняется обмен code и где хранится секрет.

Что делает SDK

SDK Authoriza:

  • создаёт state, nonce и PKCE-пару;
  • сохраняет данные незавершённого flow;
  • проверяет callback;
  • получает discovery и ключи JWKS;
  • проверяет ID Token;
  • сохраняет сессию;
  • обновляет Access Token через Refresh Token;
  • сообщает ошибку через onError.

Ошибки

INVALID_STATE обычно означает потерю данных flow, повторное использование callback или несоответствие state. Ошибка nonce указывает на то, что ID Token не связан с начатым flow. Ошибка redirect URI означает, что URL запроса не совпал с зарегистрированным в App.

Не исправляйте такие ошибки отключением проверок. Сначала сравните client ID, issuer, Redirect URI и состояние storage.

Как проверить результат

  • при старте входа в браузере появляется authorization request с code_challenge;
  • callback содержит code и state;
  • SDK отклоняет изменённый state;
  • SDK отклоняет ID Token с неверным nonce;
  • после обмена code пользователь появляется в auth.user;
  • повторный вызов login() при активной сессии не запускает новый flow.

Параметры запроса и их назначение

В authorization request обычно участвуют client_id, redirect_uri, response_type=code, scope, state, nonce, code_challenge и code_challenge_method=S256.

Каждый параметр отвечает за свою часть безопасности:

  • client_id выбирает зарегистрированный App;
  • redirect_uri определяет допустимый callback;
  • response_type=code выбирает возврат временного кода;
  • scope определяет запрошенные данные и возможности;
  • state связывает callback с начатым запросом;
  • nonce связывает ID Token с этим flow;
  • code_challenge связывает code с исходным клиентом.

Нельзя удалять параметры только потому, что «вход работает и без них» в тестовой среде. Некоторые проверки проявляются только при атаке или в случае параллельных вкладок.

Параллельные попытки входа

Пользователь может дважды нажать кнопку входа или открыть несколько вкладок. Клиент должен понимать, какой flow активен, и не перезаписывать его данные непредсказуемо. SDK Authoriza хранит flow отдельно и не запускает конфликтующие операции без обработки состояния.

После callback его параметры нужно убрать из адресной строки. Это снижает риск повторной обработки code и не оставляет OAuth-параметры в истории страницы.

Отказ и ошибки провайдера

Пользователь может отменить вход. В callback вместо code тогда приходит OAuth error, например login_required или другой код, предусмотренный сервером. Приложение должно показать понятное состояние и не создавать сессию по одному наличию state.

Проверка вручную без SDK

Если используется OIDC-библиотека напрямую, не копируйте реализацию SDK частями. Проверьте, что выбранная библиотека поддерживает discovery, PKCE, state, nonce, callback validation, token parsing и refresh. Если хотя бы одной критичной части нет, лучше выбрать другую библиотеку.

Пример результата для приложения

После callback приложение не должно считать наличие code признаком входа. SDK завершает flow, проверяет ответ и только затем публикует состояние:

const auth = createAuthoriza({
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'https://example.com/auth/callback',
});

const user = await auth.getUser();

if (user) {
  // Открываем защищённую часть приложения.
}

Не запускайте второй обмен code вручную поверх SDK. Это может привести к повторному использованию authorization code или конфликту с сохранённым state.

Как это реализовано в Authoriza SDK

SDK Authoriza автоматически создаёт и проверяет state, nonce и PKCE-пару, обрабатывает callback и обменивает code на токены. Для Public connection секрет не нужен. Redirect URI должен заранее присутствовать в настройках App.

Чем Authoriza может быть полезна

Если вы хотите использовать готовую реализацию этого flow для JavaScript и TypeScript, SDK Authoriza берёт на себя повторяющиеся проверки и управление сессией. После понимания процесса откройте руководство SDK.

Готовы подключить авторизацию?
Не разрабатывайте. Не отлаживайте. Не поддерживайте. Подключите уже готовое бесплатно.
Начните прямо сейчас или свяжитесь с инженерной командой для обсуждения архитектуры.
Авториза

© 2026 ООО "Авториза"
ОГРН: 1265400016419
ИНН: 5473026131
Россия, Новосибирск