Безопасная реализация OAuth 2.0 в 2026 году

Fri Sep 04 2026

Безопасная реализация OAuth 2.0 в 2026 году

Обычно проверка OAuth заканчивается, когда успешный сценарий входа работает. И это неправильная точка остановки. Важные сбои возникают при поддельном callback, неправильном issuer или принятии токена API, которому он не предназначен.

RFC 9700, опубликованный как BCP 240 в январе 2025 года, обновляет модель угроз OAuth с учетом практического опыта и новых угроз. Он обновляет RFC 6749, 6750 и 6819. OAuth 2.1 остается в разработке, и ожидается, что он будет включать эти рекомендации.

Документ draft-ietf-oauth-security-topics-update-02 от 24 июня 2026 года расширяет эту работу угрозами, выявленными после RFC 9700. И ему место в проверке backlog, а не в вашем списке текущих требований RFC MUST.

Ваши задачи по устранению пробелов в auth должны начинаться с точных redirect URI, PKCE, привязки callback к CSRF, защиты от подмены issuer и mix-up, защищенного хранения и проверок audience. А DPoP, mTLS и PAR следует оформлять как задачи, зависящие от риска. Поэтому сначала это руководство выбирает flow, затем защищает callback и токены, а после этого дает план миграции и тестирования.

В этой статье

OAuth «работает» задолго до того, как становится безопасным

Успешный вход доказывает лишь то, что работает один сценарий. Он почти ничего не говорит об инъекции authorization code, злоупотреблении redirect, путанице issuer, повторном использовании токена или утечке через поведение браузера и proxy.

Теперь стандарт отражает более широкую проблему. Но его соблюдение может нарушить совместимость со старыми экосистемами. Работающая система требует поэтапного усиления защиты, а не переписывания за один день. Документация провайдера объясняет механику интеграции; RFC 9700 задает базовый уровень безопасности.

Я могу предоставить перечень контролей, поддерживаемых стандартом. Но по одной конфигурации OAuth нельзя определить, оправдывает ли риск повторного использования токена внедрение DPoP. Прежде чем принимать решение, оцените раскрываемые данные, хранение ключей и возможности реагирования на инциденты.

Отказывайтесь от новых интеграций, использующих Implicit или Password grant, wildcard redirect URI, произвольные callback URL или обмен токена, допускающий отсутствие PKCE. Успешный сценарий уже получил достаточно внимания.

Начинайте с flow, а не с библиотеки

Для интерактивных пользователей выбирайте Authorization Code с PKCE. Public clients MUST использовать PKCE; confidential clients RECOMMENDED использовать его. «Confidential» описывает, где можно хранить client secret. Confidential clients по-прежнему получают преимущества от PKCE.

Client secret в мобильном бинарном файле — это декорация, а не аутентификация.

Client/workloadИспользуйтеМинимальные меры защитыНе используйте
Веб-приложение на стороне сервераAuthorization Code + PKCEТочный redirect URI, state, проверки issuer, защищенное серверное хранение токеновImplicit или Password grant
Native app или public clientAuthorization Code + PKCEПринудительное использование PKCE, точные правила redirect, state, платформенное хранение токеновВстроенные webview или client secret, рассматриваемый как доказательство
Browser applicationAuthorization Code + PKCEPKCE, защита callback, ограниченная доступность токеновТокены в URL или незащищенное browser-readable storage
Machine-to-machine serviceClient CredentialsАутентификация client, токены с ограниченным audience, защищенные credentialsИнтерактивные user grants
Существующая legacy-интеграцияМиграция в соответствии с подходящей строкойТелеметрия, поэтапное усиление, проверка совместимостиЕще одно постоянное исключение

Для multi-tenant SaaS-интеграции календаря веб-приложение является интерактивным authorization-code client независимо от того, подключается ли оно к Google или Microsoft. Отдельно проверьте его high-value API. Запланированный backend-процесс, вызывающий API без пользователя, относится к строке Client Credentials.

Для задач machine-to-machine укажите метод аутентификации, поддерживаемый вашим authorization server, требуйте токен с ограниченным audience и храните credential безопасно. Client Credentials определяет grant; сам по себе он не решает задачу аутентификации service.

Документация Google для web-server и документация Microsoft по authorization code описывают механику конкретных провайдеров. Используйте эти детали, но не позволяйте удобству провайдера отменять базовый уровень безопасности.

Flow на уровне протокола должен привязывать code к client

Создавайте новый code_verifier с высокой энтропией для каждого authorization request. Получайте его challenge с помощью SHA-256 и кодирования base64url:

code_challenge = BASE64URL(SHA256(code_verifier))

Для подключения календаря последовательность запросов выглядит так:

# Authorization request
GET /authorize?
  response_type=code&
  client_id=calendar-web&
  redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback%2Fgoogle&
  scope=calendar.read&
  state=<random-state>&
  code_challenge=<base64url-sha256-verifier>&
  code_challenge_method=S256

# Token exchange over the back channel
POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
client_id=calendar-web&
redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback%2Fgoogle&
code=<authorization-code>&
code_verifier=<original-verifier>

Браузер передает authorization request и полученный короткоживущий code. Ваш backend обменивает этот code по back channel. Если server требует redirect_uri на обоих этапах, отправляйте одно и то же зарегистрированное значение. Несовпадения отклоняйте.

Если authorization request включает PKCE challenge, authorization server MUST отклонить token request без соответствующего verifier. Это закрывает downgrade-атаку, при которой посредник удаляет PKCE из обмена.

Храните verifier вместе с ожидающей серверной транзакцией. Удаляйте его после успешного или неудачного обмена. Никогда не принимайте verifier из несвязанной browser session.

Рекомендации Google по PKCE и DPoP объясняют, как эти два контроля защищают разные части flow.

Redirect URI — это allowlist, а не шаблон

Регистрируйте полные redirect URI и сравнивайте полное значение. RFC 9700 гласит:

«Authorization servers MUST compare client redirection URIs exactly, except for port numbers in localhost redirection URIs of native apps.»

Например, если вы зарегистрировали:

https://app.example.com/oauth/callback/google

server должен отклонить:

https://app.example.com/oauth/callback/google/tenant-admin

Wildcard redirect URI — это allowlist, который кто-то забыл закончить. Сопоставление по префиксу и нестрогое сопоставление path могут отправить code на непредусмотренный endpoint. PKCE связывает обмен с verifier; проверка redirect остается отдельным контролем.

Явно определите правило сравнения и протестируйте каждое различие в scheme, host, port, path и query. Применяйте исключение для порта localhost только к localhost redirect native app. Никогда не принимайте callback URL, переданный динамически.

Open redirect рядом с callback path создает relay для чувствительных ответов. Endpoint, принимающий ?next= и перенаправляющий куда угодно, не должен участвовать в transaction path.

Рецензируемое исследование проверки redirect URI выявило слабые места между предполагаемой политикой и развернутой проверкой. Используйте это предупреждение, чтобы протестировать собственную реализацию.

  • Сравнивайте точную зарегистрированную строку. Важны scheme, host, port, path и query.
  • Отклоняйте wildcards и префиксы. «Начинается с нашего домена» — не правило безопасности.
  • Выбирайте из зарегистрированных значений. Не позволяйте параметрам request создавать callback.
  • Удаляйте open redirect. Callback должен завершать транзакцию и оставлять пользователя в рамках transaction flow.

Рассматривайте callback как недоверенную границу безопасности

Ваш callback получает данные, контролируемые атакующим. Обрабатывайте его как API endpoint, который просто поступает через браузер.

Достаточно ли PKCE?

Только при определенных условиях. PKCE может заменить защиту CSRF на основе state, если вы подтвердили, что authorization server поддерживает PKCE. Для развертывания с несколькими провайдерами state остается более безопасным вариантом по умолчанию, поскольку связывает ответ с транзакцией исходного браузера.

Генерируйте уникальный, непредсказуемый state. Связывайте его с пользовательской сессией, сравнивайте точно, затем используйте один раз. Рекомендации Auth0 по state описывают это назначение — привязку транзакции.

pending = load_transaction(returned_state)

if pending is missing or expired:
    reject

if pending.session_id != current_session.id:
    reject and consume transaction

if returned_state != pending.state:
    reject and consume transaction

if returned_issuer != pending.expected_issuer:
    reject and consume transaction

exchange code using:
    pending.client_id
    pending.registered_redirect_uri
    pending.code_verifier

validate issuer and audience wherever the received token format exposes
and supports those claims; otherwise use the provider's documented
introspection or validation mechanism

map identity by (issuer, subject)
consume transaction
create application session

Поддержка Google и Microsoft создает обязательную задачу защиты от mix-up. Используйте параметр iss, определенный RFC 9207, или отдельные redirect URI для каждого authorization server:

/oauth/callback/google
/oauth/callback/microsoft

Сохраняйте identity как (issuer, subject). Значение subject без его issuer неоднозначно.

Для OIDC проверяйте nonce в ID token как дополнительную проверку replay и привязки ответа; сохраняйте меры защиты callback, требуемые вашим flow. state, PKCE, проверки issuer и nonce выполняют разные задачи.

Не храните code и токены в местах, которые их запоминают или повторяют

Authorization code могут утекать через видимые браузеру каналы; access и refresh token создают риск повторного использования после выдачи. RFC 9700 указывает на referrer headers, browser history, утечки на resource server и случайную пересылку credentials через HTTP 307 redirect.

Не помещайте credentials в query strings и вообще в URL. Устанавливайте подходящий Referrer-Policy. Оперативно очищайте чувствительное состояние callback. Проверяйте поведение reverse proxy при redirect.

СредаПодход к хранениюОсновная граница
Server-side web appЗащищенное server-side хранилище; client credentials в secret managerНе допускайте попадания токенов в browser responses и логи
Native mobile appAndroid Keystore, iOS Keychain или Windows Credential LockerИспользуйте полноценный browser или platform OAuth library, а не Android WebView или iOS WKWebView
Browser applicationПредпочтительно backend-for-frontend и server sessionЕсли токены остаются в браузере, документируйте подверженность XSS и используйте поддерживаемый провайдером authorization-code pattern

Избегайте размещения refresh или access token в localStorage: JavaScript, запущенный из-за XSS-уязвимости, может прочитать их. Для server-side connector храните refresh token в защищенном datastore, ограничивайте доступ к расшифровке там, где используется шифрование, скрывайте токены в логах, а при отключении отзывайте и удаляйте их.

Для refresh token реализуйте rotation, если она поддерживается, обнаруживайте reuse, отзывайте токены при подозрении на компрометацию и удаляйте их, когда они больше не нужны.

По возможности отключайте redirect из token request. В противном случае убедитесь, что HTTP client не может пересылать запросы с credentials на другой origin.

Определите, когда bearer-токенов недостаточно

Bearer access token работает для любого, кто его предъявит. Ограничение audience сужает область, в которой токен принимается. Sender constraint требует владения связанным client key или certificate.

DPoP связывает token с key pair, которым владеет client. Он работает только тогда, когда authorization server выдает sender-constrained token, а resource server проверяет proof. Это означает генерацию ключей и защиту их хранения. Вам также понадобятся подписанные request proof, обработка времени, обнаружение replay, rotation и реагирование на инциденты.

Используйте это правило:

  • Обычный внутренний API с низким воздействием: сначала реализуйте базовые меры и ограничение audience.
  • Public client или high-value API: настоятельно рассмотрите DPoP.
  • Регулируемая или привязанная к инфраструктуре среда: оцените DPoP в сравнении с mTLS и с учетом ограничений вашего развертывания.

Для high-value API SaaS-приложения DPoP может оправдать операционные затраты, если повторное использование bearer token раскроет чувствительные данные tenant. Храните DPoP key в защищенном key store backend. Ключ, хранящийся в браузере, имеет другую модель компрометации и требует отдельной проверки.

PAR и sender constraint — полезная security plumbing, но внедрение любого из них повсюду превратило бы условный контроль в формальность.

Запрашивайте меньше доступа, а затем переживайте отказ

Запрос всех разрешений при первом входе дает client доступ, который ему может не понадобиться, и увеличивает трение для пользователя. Запрашивайте scopes, когда пользователь переходит к функции, которой они нужны.

  1. Сопоставьте каждую функцию с требуемыми scopes.
  2. Запрашивайте базовые scopes при первоначальном подключении.
  3. Запрашивайте повышенный scope, когда пользователь активирует соответствующую функцию.
  4. Если пользователь отказывает, отключите эту функцию и оставьте остальные доступными.
  5. Объясните причину перед повторным запросом.
  6. Удалите неиспользуемые clients и credentials.

Функция чтения календаря может оставаться доступной, если в разрешении на запись в календарь отказано. Не превращайте один отклоненный scope в неудачный вход для всего приложения.

Google сообщает, что автоматически удаляет OAuth clients, неактивные в течение шести месяцев. Это специфическая для Google операционная рекомендация, поэтому proactively удаляйте неиспользуемые clients, а не полагайтесь на эту политику.

Добавляйте PAR там, где сам authorization request требует защиты

Pushed Authorization Requests передают параметры authorization через back channel до того, как браузер достигает authorization endpoint. Затем браузер передает короткую ссылку на request вместо полной authorization payload.

Используйте PAR, когда его поддерживают ваш authorization server и client, и проверяйте, что authorization request, использованный во front channel, совпадает с отправленным request. oauth.net называет PAR предпочтительным для развертываний с высоким уровнем безопасности.

Рассматривайте PAR как workload-specific control. Он оправдан, когда целостность request, раскрытие параметров или требования к assurance оправдывают еще один endpoint и еще один операционный путь.

Устраняйте пробелы в auth без остановки production

Сначала проведите инвентаризацию, потому что enforcement выявит clients, зависящих от устаревшего поведения. Зафиксируйте каждый client, authorization server, redirect URI, grant и scope. Также зафиксируйте lifetime токена, место хранения и audience resource server. Затем классифицируйте clients как public, confidential, native, browser или machine-to-machine.

Базовое enforcement сейчас

Отклоняйте новые интеграции Implicit и Password. Для существующих clients внедрите точную регистрацию redirect, PKCE, enforcement verifier, state, валидацию issuer и защиту от mix-up. Переместите secrets и токены в защищенное хранилище, затем добавьте ограничение audience.

Для каждого изменения назначьте владельца, метрику сбоев, switch для rollback и дату вывода из эксплуатации. Сначала применяйте enforcement к новым clients, затем переносите существующие clients по когортам.

Условное усиление защиты — следующим этапом

Оцените DPoP или mTLS для sender-constrained tokens. Добавьте PAR там, где authorization request требует защиты. Эти меры зависят от workload, поддержки провайдера, хранения ключей и способности resource server проверять получаемые данные.

Доказательства перед enforcement

Вводите каждое правило поэтапно, используя телеметрию для отслеживания legacy-сбоев. Client, который ломается после enforcement точного redirect, должен получить владельца и путь миграции. Тихие исключения превращаются в долг. Совместимость, для которой требуется принимать атаку, — это долг с бюджетом доступности.

Документ -02 от июня 2026 года относится к этой проверке backlog. Это черновик IETF, а не финальный RFC. Используйте его для подготовки тестов и будущих задач, но не представляйте его предложения как текущие обязательные требования.

Тестируйте негативные сценарии и кнопку входа

Стройте проверку на парах «атака и мера защиты» из стандарта. Тестируйте:

  • измененный или незарегистрированный redirect URI;
  • сопоставление по префиксу, wildcard и путаницу path;
  • open redirect в callback;
  • отсутствующий, несовпадающий, повторно использованный или угаданный state;
  • неправильный или отсутствующий issuer;
  • обмен authorization code без verifier;
  • неправильный verifier и повторное использование verifier;
  • подмененный authorization code;
  • token с неправильным audience;
  • появление токенов и code в query strings;
  • отсутствие подходящего Referrer-Policy в callback response;
  • replay и обнаружение повторного использования refresh token;
  • redirect из token endpoint, пересылающий credentials через 307;
  • mix-up с несколькими authorization server;
  • обход PKCE для public client.

Для теста redirect ожидайте отклонения до передачи code. Тест callback должен завершаться ошибкой до создания session. Тесты токенов должны заставить server или resource server отклонить значение и записать событие, не занося credential в лог.

Изучите обновление черновика IETF от 24 июня 2026 года, посвященное возникающим угрозам. Это по-прежнему draft.

Перед rollout требуйте зафиксированные сбои для незарегистрированного redirect, отсутствующего или неправильного verifier, несовпадающего state, неправильного issuer или audience и повторно использованного refresh token. Заблокируйте release, пока эти пять тестов не будут завершаться ошибкой в CI или эквивалентной среде security testing.