
Когда две программы на одном устройстве сообщают операционной системе, что обе поддерживают обработку ссылок вида myapp://callback, возникает проблема. Пользователь нажимает «Войти через Google», браузер передаёт код авторизации обратно в приложение, но система не может определить, какому именно приложению его отдать. Согласно RFC 7636, такая ситуация описана сухим техническим языком, но фраза о реальных случаях её применения заставляет задуматься.

Именно для устранения этой уязвимости был разработан PKCE (Proof Key for Code Exchange), произносится как «пикси». Это расширение протокола OAuth 2.0, опубликованное в сентябре 2015 года. Авторами спецификации выступили Нат Сакимура из Nomura Research Institute, Джон Брэдли из Ping Identity и Навин Агарвал из Google. Простота их решения сделала его обязательным для публичных клиентов спустя десятилетие.
Принцип работы кода авторизации
OAuth решает важную задачу: предоставление доступа к данным пользователя на стороннем сервисе без передачи пароля. Наиболее распространённый сценарий Authorization Code Grant основан на использовании кратковременного кода.
Процесс выглядит следующим образом: приложение перенаправляет пользователя на сервер авторизации с параметрами response_type=code, идентификатором клиента и адресом возврата. После аутентификации и подтверждения разрешений сервер возвращает не токен, а временный код, который затем обменивается на токен через отдельный endpoint. Этот обмен происходит без участия браузера.
Основное преимущество такой схемы в том, что для конфиденциальных клиентов финальный обмен подтверждается секретом (client_secret). Однако эта защита не работает для публичных клиентов, у которых нет возможности надёжно хранить секреты.
Проблема хранения секретов в мобильных приложениях
Серверные приложения могут хранить client_secret в защищённых переменных окружения, тогда как публичные клиенты лишены этой возможности. Нативные приложения для Android легко декомпилируются, а исходный код веб-приложений доступен прямо в браузере. Даже использование защищённых хранилищ на iOS или Android не обеспечивает полной безопасности на устройствах с джейлбрейком.
Таким образом, любой client_secret, встроенный в мобильное или браузерное приложение, фактически становится публичным. Это делает перехваченный код авторизации уязвимым.
Атака, для предотвращения которой создан PKCE
На мобильных платформах приложения взаимодействуют с браузером через кастомные URI-схемы. Несколько приложений могут зарегистрировать одну и ту же схему, что делает получателя ссылки неопределённым. Вредоносное приложение может просто заявить поддержку нужной схемы и перехватывать коды авторизации.
RFC 7636 подтверждает, что такие атаки уже наблюдались в реальных условиях.
Механизм работы PKCE
Решение отличается простотой и эффективностью. Приложение генерирует случайную строку (code_verifier) длиной от 43 до 128 символов, используя криптостойкий генератор. Из этой строки вычисляется производная (code_challenge) по методу S256: BASE64URL(SHA256(code_verifier)). Именно code_challenge отправляется на сервер авторизации вместе с параметром code_challenge_method=S256.

Аналогия с замком и ключом помогает понять принцип: замок (code_challenge) остаётся на виду, а ключ (code_verifier) хранится в секрете. При обмене кода на токен приложение отправляет исходный code_verifier, который сервер проверяет путём повторного вычисления code_challenge.
Это предотвращает использование перехваченного кода, так как злоумышленник не имеет доступа к code_verifier.
Сравнение методов plain и S256
Существует два метода реализации PKCE, различающихся по уровню защиты.
| Метод | Формирование challenge | Уровень защиты |
|---|---|---|
| plain | исходный code_verifier без изменений | защищает только от узкого круга атак |
| S256 | SHA-256 от code_verifier в формате base64url | обеспечивает стойкую защиту |
Важно отметить, что по умолчанию сервер использует метод plain, если параметр code_challenge_method не указан. Это снижает уровень защиты, поэтому рекомендуется всегда явно указывать S256.
Типы атак и их различия
В контексте PKCE часто смешивают три различных сценария атак:
- Перехват кода: вредоносное приложение получает код через кастомную схему URI.
- Инъекция кода: злоумышленник подменяет легитимный код своим.
- Понижение уровня защиты: сервер поддерживает PKCE, но не требует его использования.
PKCE не заменяет аутентификацию
Важно понимать, что PKCE не заменяет client_secret и не превращает публичного клиента в конфиденциального. client_secret подтверждает личность приложения, а code_verifier подтверждает владение конкретной транзакцией авторизации.
PKCE полезен даже для клиентов, имеющих client_secret, так как защищает от инъекции кода.
Взаимодействие с state и nonce
PKCE не отменяет необходимость использования параметров state (защита от CSRF) и nonce (обеспечение свежести ID-токена в OpenID Connect). Хотя RFC 9700 допускает использование PKCE для защиты от CSRF, на практике рекомендуется сохранять все уровни защиты.
Ограничения PKCE
PKCE защищает только код авторизации, но не предотвращает кражу уже выданных токенов. Для защиты токенов используются другие механизмы, такие как DPoP (RFC 9449) или взаимный TLS (RFC 8705).
Эволюция PKCE
За десять лет PKCE прошёл путь от рекомендации до обязательного требования. RFC 9700, выпущенный в январе 2025 года, сделал PKCE обязательным для публичных клиентов. Черновик OAuth 2.1 планирует сделать PKCE обязательным для всех клиентов.
Интересно, что PKCE нашёл применение в ИИ-агентах, где традиционные методы хранения секретов часто неудобны.
Рекомендации по реализации в 2026 году

- Использовать только метод S256.
- Генерировать code_verifier с помощью криптостойкого генератора для каждого запроса.
- Хранить code_verifier временно и удалять после использования.
- Требовать code_challenge на сервере авторизации и проверять code_verifier при обмене кода.
- Не использовать client_secret в мобильных и SPA-приложениях.
- Предпочитать HTTPS-редиректы кастомным схемам.
- Сохранять параметры state и nonce.
Ответы на частые вопросы
Нужен ли PKCE при наличии client_secret?
Да, так как PKCE защищает от инъекции кода, что не обеспечивается client_secret.
Можно ли использовать метод plain?
Технически возможно, но не рекомендуется из-за низкого уровня защиты.
Как генерировать code_verifier?
Использовать криптостойкий генератор для создания строки длиной 43-128 символов из определённого набора символов.
Заменяет ли PKCE параметр state?
В общем случае нет, рекомендуется использовать оба механизма.
Защищает ли PKCE выданные токены?
Нет, для защиты токенов нужны другие механизмы.
В чём разница между PKCE и nonce?
nonce обеспечивает защиту на уровне приложения, а PKCE защищает код авторизации.
Чем заменить кастомные URI-схемы?
Использовать App Links (Android) или Universal Links (iOS).
Является ли PKCE обязательным?
Фактически да для публичных клиентов, формально станет обязательным с принятием OAuth 2.1.
Интересно отметить, что многие разработчики включают PKCE просто потому, что это ассоциируется с повышенной безопасностью. За этим названием скрывается эффективный криптографический механизм, решающий конкретную проблему. Джон Брэдли, один из авторов PKCE, участвовал как в создании RFC 7636 в 2015 году, так и в разработке RFC 9700 в 2025 году, что подчёркивает важность этого механизма.
