Защита OAuth 2.0 в SPA и мобильных приложениях через PKCE

В первые годы существования стандарта OAuth 2.0 (RFC 6749) основным способом получения access token был серверный Authorization Code Flow с использованием client_id и client_secret. Однако с бурным ростом фронтенд-фреймворков (React, Vue, Nuxt) и мобильных приложений возникла критическая уязвимость: секрет приложения (client_secret), встроенный в клиентский код, легко компрометировался.
Проблема безопасности в публичных клиентах (Public Clients)
Публичные клиенты (SPA в браузере, мобильные и десктопные приложения) физически не способны защитить секретный ключ. Любой пользователь может открыть Chrome DevTools или декомпилировать приложение и извлечь client_secret.
Кроме того, при перенаправлении пользователя обратно на redirect_uri злоумышленник (через вредоносное расширение браузера или кастомный схематичный URI на мобильном устройстве) мог перехватить code и обменять его на токен, используя украденный client_secret.
Скандальное рождение OAuth 2.0 (2012)
OAuth 2.0 появился не тихо. Эран Хаммер (Eran Hammer), ведущий автор и редактор спецификации, в июле 2012 года покинул рабочую группу — за несколько месяцев до публикации RFC 6749 — и опубликовал пост под названием «OAuth 2.0 and the Road to Hell» («OAuth 2.0 и дорога в ад»). Его претензия: спецификация стала «сложнее, менее совместимой, менее полезной, более незавершённой и, главное, менее безопасной», чем OAuth 1.0, отказавшись от обязательных подписей в пользу bearer-токенов и оставив слишком много деталей на усмотрение каждого вендора. RFC 6749 всё равно вышел в октябре того же года — уже без его имени. PKCE во многом существует, чтобы залатать именно ту дыру, о которой предупреждала эта критика.
Как работает PKCE (RFC 7636)
В сентябре 2015 года Nat Sakimura, John Bradley и Naveen Agarwal опубликовали RFC 7636, и изначально он писался вовсе не для SPA — мотивирующий сценарий был про нативные мобильные приложения. Два приложения могли зарегистрироваться на одну и ту же кастомную URI-схему (myapp://callback), и вредоносное приложение могло перехватить код авторизации, предназначенный для легитимного. PKCE (произносится «пикси») закрыл эту дыру, а позже выяснилось, что та же техника отлично подходит и браузерным публичным клиентам — как только индустрия осознала, что client_secret в JavaScript ничем не безопаснее, чем в мобильном бинарнике.
Proof Key for Code Exchange исключает необходимость хранить client_secret на клиенте и работает по следующему алгоритму:
Client Browser (SPA) Auth Server (IDP)
| |
|---- (A) Authorization Request ------->|
| + code_challenge (S256) |
| |
|<--- (B) Authorization Code -----------|
| |
|---- (C) Token Request --------------->|
| + code_verifier |
| |
|<--- (D) Access Token -----------------|
| |
- Генерация пары на клиенте:
- Браузер генерирует криптографически стойкую случайную строку
code_verifier(от 43 до 128 символов). - Вычисляется
code_challenge:Base64URL(SHA256(code_verifier)).
- Браузер генерирует криптографически стойкую случайную строку
- Запрос авторизации:
- Клиент отправляет пользователя на сервер авторизации, передавая
client_id,redirect_uri,code_challengeиcode_challenge_method=S256.
- Клиент отправляет пользователя на сервер авторизации, передавая
- Обмен кода на токен:
- После успешного входа сервер авторизации возвращает
code. - Клиент делает
POST-запрос на/token, передавая исходный необработанныйcode_verifier. - Сервер авторизации сам вычисляет
SHA256(code_verifier)и сравнивает с сохраненнымcode_challenge. Если хэши совпали — выдаетсяaccess_token.
- После успешного входа сервер авторизации возвращает
Возможности нашего OAuth PKCE Generator
Инструмент OAuth 2.0 PKCE Generator & Auth Flow Sandbox позволяет отлаживать весь цикл авторизации:
- Генерация PKCE пар (S256): мгновенное вычисление
code_verifierиcode_challengeчерез стойкий Web Crypto API (window.crypto.subtle). - Сборщик Auth URL: пресеты для Google OAuth 2.0, GitHub, Microsoft Entra ID (Azure AD) и Auth0.
- Генератор cURL запроса обмена: готовая команда для терминала для выполнения
POST /token. - Парсер Callback URL: разбор параметров
code,stateиerrorиз адресной строки после перенаправления.
Быстрый старт: откройте тул → сгенерируйте новую PKCE-пару → перейдите на вкладку Auth URL Builder → выберите пресет провайдера → скопируйте URL и откройте его в новой вкладке → после входа вставьте callback-URL в парсер → используйте полученный code вместе с вашим code_verifier во вкладке Token Exchange cURL.
Похожие инструменты
Частые вопросы
Зачем нужен PKCE (Proof Key for Code Exchange)?
В одностраничных веб-приложениях (SPA) и мобильных клиентах невозможно безопасно хранить Client Secret (он может быть извлечен из JS-кода или декомпилированного APK). PKCE предотвращает перехват авторизационного кода (Authorization Code Interception Attack) за счет генерации одноразовой динамической криптографической пары верификатора и хэша вызова.
Чем отличаются методы code_challenge_method plain и S256?
При методе plain значение code_challenge передается в открытом виде как копия code_verifier, что уязвимо к прослушиванию трафика. Метод S256 наносит SHA-256 хэш на code_verifier и кодирует результат в Base64URL — это золотой стандарт безопасности RFC 7636.
Какая допустимая длина у code_verifier?
Согласно спецификации RFC 7636, строка code_verifier должна иметь длину от 43 до 128 символов и состоять только из не зарезервированных символов URL [A-Z, a-z, 0-9, -, ., _, ~].
Автор
Alex RelFullstack Engineer
В коммерческой разработке с 2011 года — больше 15 лет. Автор двух SaaS-продуктов: TeleGo.io и LingvoHabit.com. Статьи здесь выросли из реальной клиентской и продуктовой работы.



