Атака, которую побеждает PKCE

В потоке кода авторизации 2.0 сервер авторизации вручает клиенту короткоживущий код авторизации, который клиент затем обменивает на токены. Конфиденциальные клиенты защищают этот обмен секретом клиента. Но публичные клиенты, одностраничные приложения и мобильные приложения, не могут хранить секрет: что угодно, доставленное в браузер или на устройство, может быть извлечено. Это оставляет брешь. Если атакующий перехватит код авторизации (через вредоносное приложение, зарегистрированное на тот же редирект, утёкший лог или подделанный URL), он мог бы сам обменять его на токены.

, Proof Key for Code Exchange (RFC 7636, произносится «pixy»), затыкает эту брешь, не требуя заранее общего секрета. Он привязывает код авторизации к одноразовому секрету, который знает только подлинный клиент.

Как verifier и challenge сочетаются

Поток добавляет два значения:

  • code_verifier — это случайная строка высокой энтропии, которую клиент генерирует и хранит. RFC 7636 требует от 43 до 128 символов из незарезервированного набора (буквы, цифры и - . _ ~).
  • code_challenge выводится из verifier. Для метода S256 это BASE64URL(SHA256(ASCII(code_verifier))).

Последовательность такова:

  1. Клиент генерирует свежий code_verifier, выводит code_challenge и отправляет только challenge в запросе авторизации.
  2. Сервер авторизации сохраняет challenge и возвращает код авторизации как обычно.
  3. При запросе токена клиент отправляет verifier.
  4. Сервер хеширует verifier тем же способом и сверяет его с сохранённым challenge. Нет совпадения — нет токенов.

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

Почему S256, а не plain

RFC 7636 определяет два метода. Метод plain устанавливает challenge равным verifier, что не даёт никакой защиты, если наблюдается сам запрос авторизации. Метод S256 отправляет лишь хеш , так что verifier остаётся скрытым до финального, защищённого TLS вызова токена. S256 обязателен везде, где клиент может вычислить SHA-256, то есть везде, где это важно; plain существует лишь для редкого ограниченного устройства, которое не может хешировать.

От необязательного к обязательному

PKCE начинался как расширение для публичных клиентов, но рекомендации решительно сдвинулись. OAuth 2.0 Security Best Current Practice (RFC 9700) теперь требует PKCE на всех клиентах кода авторизации, включая конфиденциальные, потому что он также защищает от определённых атак внедрения кода. Если вы строите или тестируете интеграцию OAuth или OpenID Connect сегодня, считайте PKCE значением по умолчанию, а не дополнением.

Инструмент PKCE генерирует соответствующий verifier, выводит его challenge S256 с помощью Web Crypto и проверяет любой verifier против правил длины и набора символов RFC, всё локально. Verifier никогда не покидает ваш браузер.