OAuth 2.0 Flows Авторизация Code, PKCE и Client Credentials

Обновлено

Руководство для разработчиков по выбору правильного потока OAuth 2.0. Охватывает поток авторизации (веб-приложения), PKCE (SPA и мобильные приложения) и поток клиентских учетных записей (сервер-на-сервер) с рабочими примерами кода и ошибками, которые могут повлиять на вас позже.

OAuth 2.0 Flows: Authorization Code, PKCE, and Client Credentials 1
Реклама · УДАЛИТЬ?

Три потока охватывают 95% реальных случаев использования OAuth 2.0. В спецификации описаны больше случаев, но остальные устарели, являются крайними случаями или оба. Выберите правильный поток на начальном этапе и вы избежите болезненной перестройки при изменении требований к аутентификации.

ПотокКогда их использоватьУчаствует ли пользователь?Требуется client_secret?
Авторизация CodeВеб-приложение с серверомДаДа — остается на сервере
Авторизация Code + PKCESPA, мобильное приложение, любой публичный клиентДаНет
Client CredentialsСервер-к серверу (без пользователя)НетДа — остается на сервере

Поток авторизации кода

Стандартный поток для веб-приложений с сервером. Токены доступа и секреты клиента никогда не достигают браузера — обмен токенами происходит на сервере. Это и есть основная цель.

Шаг 1: Перенаправьте пользователя

Создайте URL авторизации и перенаправьте браузер на него:

GET https://accounts.example.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid+profile+email
  &state=RANDOM_CSRF_TOKEN

The state значение является вашей защитой от CSRF при перенаправлении. Генерируйте уникальную случайную строку на каждом потоке, сохраняйте её в сессии и проверяйте при возврате пользователя. Если вы пропустите эту проверку, атакующий может привести пользователя через поток, используя свой собственный код авторизации — безвозвратно связывая аккаунт пользователя с идентичностью атакующего.

Шаг 2: Обработайте ответ

Авторизационный сервер перенаправляет вас redirect_uri с кратковременным кодом:

GET https://yourapp.com/callback?code=AUTH_CODE&state=SAME_STATE_YOU_SENT

Проверить state соответствует тому, что вы сохранили. Затем обмен кода происходит на сервере.

Шаг 3: Обмен кода на токены (только на сервере)

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Ответ:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200a12b3c...",
  "scope": "openid profile email"
}

Сохраняйте оба токена на сервере. Когда access_token истекает (проверьте expires_in), используйте refresh_token для получения нового токена без необходимости перенаправлять пользователя на вход.

PKCE — Для клиентов, которые не могут хранить секреты

SPA или мобильное приложение не имеют безопасного места для хранения client_secret. Любой может открыть DevTools и найти его в вашем JS-пакете. Любой может дешифровать ваш APK. PKCE (Proof Key for Code Exchange, произносится как «pixy») решает эту проблему с помощью одноразового криптографического вызова — не требуется общий секрет.

Поток идентичен потоку авторизации кода с двумя дополнениями: code_verifier (случайная строка, которую вы генерируете) и code_challenge (SHA-256 хэш верификатора, закодированный в формате base64url). Вы отправляете вызов в начале, затем доказываете, что у вас есть верификатор, при обмене токенами.

Шаг 1: Генерируйте верификатор и вызов

function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64url(array);
}

async function generateCodeChallenge(verifier) {
  const data = new TextEncoder().encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return base64url(new Uint8Array(digest));
}

function base64url(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier in memory — NOT localStorage

Сохраняйте code_verifier в памяти — переменную в рамках модуля, а не в localStorage. Вы отправите её при обмене токенами.

Шаг 2: Запрос авторизации — добавьте вызов

GET https://accounts.example.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=openid+profile
  &state=RANDOM_CSRF_TOKEN
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

Шаг 3: Обмен токенами — верификатор вместо client_secret

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=ORIGINAL_VERIFIER_YOU_GENERATED

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

Значимо знать: OAuth 2.1 (современная модернизация OAuth 2.0, в разработке) требует PKCE для всех потоков, включающих перенаправление. Если вы пишете новое код, используйте PKCE независимо от того, требует ли ваш провайдер это сейчас.

Client Credentials — Без пользователя, без проблем

Фоновые задачи, микросервисы, вызывающие другие микросервисы, задачи cron, обращающиеся к API — ни один из этих случаев не включает пользователя. Правильный поток — Client Credentials: сервис аутентифицируется напрямую с помощью своего client ID и секрета.

POST https://accounts.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=api:read api:write

Ответ:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 86400
}

Нет токена обновления — когда он истекает, просто запросите новый. Обычная ошибка: каждый вызов API отправляет запрос на токен. Кэшируйте токен, проверяйте срок действия перед каждым запросом, перезапрашивайте только тогда, когда он скоро истечёт. Один запрос токена в день (или в час, в зависимости от expires_in) вместо одного на каждый запрос:

let cachedToken = null;
let tokenExpiresAt = 0;

async function getAccessToken() {
  // Refresh 30 seconds before actual expiry
  if (cachedToken && Date.now() < tokenExpiresAt - 30_000) {
    return cachedToken;
  }

  const res = await fetch('https://accounts.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET,
      scope: 'api:read api:write',
    }),
  });

  const data = await res.json();
  cachedToken = data.access_token;
  tokenExpiresAt = Date.now() + (data.expires_in * 1000);
  return cachedToken;
}

Ошибки, которые на самом деле совершают разработчики

Хранение токенов в localStorage

Любая уязвимость XSS — в вашем коде, в зависимости, в скрипте тег-менеджера — может прочитать всё, что хранится в localStorage. Для SPA: храните токен доступа в памяти (переменная в рамках модуля, исчезающая при перезагрузке). Используйте httpOnly куки для токенов обновления, если у вас есть сервер, который может их устанавливать. JavaScript не может прочитать httpOnly куки.

Использование потока implicit

Поток implicit возвращает токены напрямую в фрагменте URL (#access_token=...). Эти токены попадают в историю браузера, логи сервера и заголовки Referer. Он был отменён в RFC 9700. Нет причин использовать поток implicit для нового кода. Используйте PKCE.

Пропуск проверки state

Скачать как .yml state Проверка на ответе, атакующий может создать URL-адрес перенаправления, который завершает поток авторизации с использованием собственного кода авторизации. Результат: аккаунт пользователя привязывается к идентичности атакующего на стороне провайдера. Генерируйте его каждый раз при потоке, сохраняйте в сессии и проверяйте при возврате.

Положение client_secret в коде фронтенда

Такого понятия, как секрет, живущий в браузере, не существует. Минификация не скрывает его. Обфускация не защищает его. Если ваша среда — браузер или мобильное приложение, у вас есть публичный клиент — используйте PKCE и полностью исключите client_secret. Это не работающий вариант; это то, как спецификация предполагает работу публичных клиентов.

Не обрабатывает истечение токена заранее

Каждый токен доступа имеет значение expires_in . Если вы держите токен до тех пор, пока он не выдает ошибку 401, и затем перезапрашиваете его, пользователи сталкиваются с неожиданными ошибками. Проверяйте срок действия перед каждым запросом, обновляйте токен заранее (30 секунд до истечения — разумный буфер), и обрабатывайте редкий случай, когда сам токен обновления истек.

Проверка токенов во время работы

Большинство поставщиков OAuth выдают JWT в качестве токенов доступа. Тело пакета закодировано в формате base64url и читается без приватного ключа — только подпись требует ключа для проверки. Когда вы отладываете поток и хотите увидеть утверждения, диапазоны или срок действия в токене, вставьте его в Декодер JWT.

Если вы ручно проверяете PKCE и хотите увидеть, что декодирует base64url-строка code_challenge в формате, то Конвертер base64 обрабатывает стандартные и URL-безопасные варианты.

Краткое объяснение

Один вопрос определяет правильный поток: имеет ли ваша среда безопасное место для хранения секрета?

  • Бэкенд-сервер → Авторизация Code или Client Credentials (секрет остаётся на сервере)
  • Браузер или мобильное приложение → Авторизация Code + PKCE (никакого секрета вообще)
  • Без участия пользователя → Client Credentials

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

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?