OAuth 2.0 Flows Авторизация Code, PKCE и Client Credentials
Руководство для разработчиков по выбору правильного потока OAuth 2.0. Охватывает поток авторизации (веб-приложения), PKCE (SPA и мобильные приложения) и поток клиентских учетных записей (сервер-на-сервер) с рабочими примерами кода и ошибками, которые могут повлиять на вас позже.
Три потока охватывают 95% реальных случаев использования OAuth 2.0. В спецификации описаны больше случаев, но остальные устарели, являются крайними случаями или оба. Выберите правильный поток на начальном этапе и вы избежите болезненной перестройки при изменении требований к аутентификации.
| Поток | Когда их использовать | Участвует ли пользователь? | Требуется client_secret? |
|---|---|---|---|
| Авторизация Code | Веб-приложение с сервером | Да | Да — остается на сервере |
| Авторизация Code + PKCE | SPA, мобильное приложение, любой публичный клиент | Да | Нет |
| 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 сделает это обязательным.
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент Добавлено 12 июня 2026 года
