OAuth 2.0 — это механизм авторизации, который предоставляет приложениям ограниченный доступ. OpenID Connect (OIDC) добавляет к нему способ передать клиенту результат аутентификации конечного пользователя.
В статье разграничены аутентификация и авторизация, назначение токенов, код авторизации с PKCE и проверка ID-токена. Код иллюстрирует отдельные операции, а не полноценный вход в систему. Для рабочего сервиса используйте библиотеку OIDC и настройки провайдера, соответствующие вашим требованиям.
1. Аутентификация и авторизация
Аутентификация / AuthN
Аутентификация проверяет личность пользователя или идентичность системы на основании представленных подтверждений. Аналогия — проверка пропуска сотрудника на входе в компанию.
Авторизация / AuthZ
Авторизация определяет, какие действия разрешены субъекту и к каким ресурсам он может обращаться. Подтверждение того, что человек является сотрудником, ещё не даёт ему доступ во все серверные.
| Параметр | Аутентификация | Авторизация |
|---|---|---|
| Вопрос | Кто это? | Что разрешено? |
| Сокращение | AuthN | AuthZ |
| Рассматриваемый механизм | OIDC передаёт результат аутентификации | OAuth 2.0 предоставляет ограниченный доступ |
| Пример токена | ID-токен OIDC | Токен доступа OAuth 2.0 |
Не каждый способ аутентификации выдаёт ID-токен. OAuth 2.0 также не определяет весь контроль доступа внутри приложения. Получив токен, API всё равно должен проверить разрешения для конкретного ресурса и действия.
Сам OAuth 2.0 не стандартизирует проверку клиентом результата аутентификации конечного пользователя. Нельзя считать вход выполненным только потому, что получен токен доступа. Используйте протокол, например OIDC, позволяющий проверить этот результат.
2. Роли и типы предоставления доступа в OAuth 2.0
2.1 Ограниченный доступ
OAuth 2.0 позволяет клиенту получить токен доступа от имени владельца ресурса или от собственного имени. Делегирование доступа без передачи пароля пользователя клиенту — важное свойство потока кода авторизации.
2.2 Четыре роли
- Владелец ресурса (Resource Owner) — субъект, способный разрешить доступ к защищённому ресурсу. Это не обязательно человек.
- Клиент (Client) — приложение, использующее защищённые ресурсы.
- Сервер авторизации (Authorization Server) — сервер, который проверяет авторизацию, аутентификацию клиента и другие условия применяемого потока, затем выдаёт токены.
- Сервер ресурсов (Resource Server) — сервер, который проверяет токены и разрешения и предоставляет защищённые ресурсы.
2.3 Основные типы грантов
Код авторизации (Authorization Code Grant)
В ответе авторизации возвращается короткоживущий код, который обменивают на токен доступа в конечной точке токенов. Этот поток с PKCE доступен как конфиденциальным клиентам, способным защищать учётные данные на сервере, так и публичным клиентам, например SPA и нативным приложениям. Размещение секрета клиента в распространяемом коде или браузере не позволяет сохранить его в тайне.
Неявный грант (Implicit Grant)
В этом случае токен доступа возвращается непосредственно в ответе авторизации. Возникают риски утечки, внедрения и повторного использования токенов. RFC 9700 рекомендует НЕ использовать этот грант (SHOULD NOT), если не выполнены указанные условия снижения рисков. Для новой конфигурации выбирайте код авторизации с PKCE. Это не означает одинакового запрета всех типов ответов OIDC.
Пароль владельца ресурса (ROPC)
При этом гранте клиент получает пароль пользователя напрямую. Хотя RFC 6749 описывает такой механизм, обновлённые рекомендации RFC 9700 ЗАПРЕЩАЮТ его использование (MUST NOT). Миграцию устаревших систем нельзя представлять как рекомендуемое исключение.
Учётные данные клиента (Client Credentials Grant)
Конфиденциальный клиент аутентифицирует себя и получает токен. Он может обращаться к ресурсам под своим контролем или к ресурсам другого владельца по предварительной договорённости с сервером авторизации. Обычно это обмен между системами без взаимодействия с конечным пользователем; клиент не обязательно становится владельцем ресурса.
2.4 Код авторизации и PKCE
PKCE (Proof Key for Code Exchange) связывает клиента, начавшего запрос авторизации, с клиентом, обменивающим код на токены. Это помогает защититься от перехвата и внедрения кодов. Для публичных клиентов PKCE обязателен, а для конфиденциальных также рекомендуется. Он не заменяет аутентификацию, необходимую конфиденциальному клиенту.
Для каждой транзакции авторизации клиент создаёт новый секретный code_verifier. RFC 7636 задаёт длину 43–128 символов из латинских букв, цифр и -._~ и рекомендует не менее 256 бит энтропии. Кодирование 32 байт от криптографически стойкого генератора случайных значений в Base64url без заполнения даёт 43 символа.
Для S256 значение challenge вычисляется так. Завершающие символы заполнения = в Base64url опускаются.
В запросе авторизации отправляют code_challenge и code_challenge_method=S256, а в запросе токена — code_verifier, сохранённый для той же транзакции. Сервер проверяет его по методу и challenge, записанным вместе с кодом. Нельзя переключаться на plain только потому, что S256 завершился ошибкой.
Общая схема потока
sequenceDiagram
autonumber
participant U as "Пользователь / браузер"
participant C as "Клиент"
participant AS as "Сервер авторизации / OP OIDC"
participant RS as "Сервер ресурсов"
Note over C: "Сохранить verifier и state для транзакции; для OIDC также nonce"
C->>U: "Перенаправить к конечной точке авторизации"
U->>AS: "Запрос: challenge и S256; для OIDC openid и nonce"
AS-->>U: "При необходимости запросить аутентификацию и согласие"
U->>AS: "Выполнить аутентификацию и авторизацию"
AS-->>U: "Перенаправить на зарегистрированный redirect_uri с code и state"
U->>C: "Обратный вызов: code и state"
Note over C: "Проверить транзакцию, state и ожидаемого издателя"
C->>AS: "Обменять code с verifier и требуемой аутентификацией клиента"
Note over AS: "Проверить код, привязки и PKCE"
AS-->>C: "Токен доступа; для OIDC также ID-токен"
Note over C: "Для OIDC проверить ID-токен и nonce"
C->>RS: "Вызвать API с токеном доступа"
RS-->>C: "Проверить токен и разрешения, затем вернуть ресурс"
Это обзор успешного потока. Защищайте обмен с помощью TLS. Реализация также должна обрабатывать проверку адресов перенаправления, однократное использование кодов, ответы с ошибками, истечение срока транзакции и защиту от атак mix-up при работе с несколькими серверами авторизации.
Генерация параметров PKCE (JavaScript / Web Crypto API)
Эти браузерные функции требуют безопасного контекста, например HTTPS. generatePkce() только возвращает параметры: она не отправляет запрос авторизации и не сохраняет транзакцию. Не выводите verifier в журналы или URL. Свяжите его с транзакцией, начатой в данном браузере, и удалите после обмена или истечения срока.
| |
Код использует все биты 32 случайных байт. Старый пример соединял только последние две шестнадцатеричные цифры каждого из 28 случайных 32-битных целых чисел: получал 896 бит, но сохранял лишь 224. Это усечение исправлено.
3. OIDC и ID-токены
3.1 Слой аутентификации поверх OAuth
В OIDC клиент (Relying Party) запрашивает аутентификацию с областью openid, а OpenID Provider (OP) возвращает результат в ID-токене. Пользователь не обязательно вводит пароль каждый раз: OP может использовать существующий сеанс или другой способ аутентификации.
3.2 Разделение назначения токенов
- Токен доступа — учётные данные для обращения к серверу ресурсов. Это может быть JWT или непрозрачная строка; OAuth 2.0 не задаёт единого формата. Клиент должен следовать требованиям API, не предполагая внутренней структуры токена.
- ID-токен — JWT с результатом аутентификации, предназначенным клиенту. До использования проверьте подпись, утверждения и привязку к запросу аутентификации. Одного декодирования недостаточно; отправлять его вместо токена доступа к API тоже нельзя.
3.3 Структура JWT и подписи
Здесь рассматривается JWS Compact Serialization для подписанного, но не зашифрованного JWT: три части, разделённые точками. JWT также может быть зашифрован как JWE или иметь вложенную структуру, поэтому не всегда состоит из трёх частей.
- Header (заголовок) — поля, например алгоритм подписи
algи идентификатор ключаkid. - Payload (полезная нагрузка) — утверждения об издателе, получателях, времени истечения срока, субъекте и других данных.
- Signature (подпись) — подпись или MAC, созданные указанным алгоритмом. Рассматриваемый RS256 использует цифровую подпись RSA.
Заголовок и полезная нагрузка лишь закодированы в Base64url. Подпись не скрывает их содержимое. См. также различия между шифрованием, хешированием и подписью.
Основные обязательные утверждения ID-токена
iss— идентификатор издателя OP. Он должен точно совпадать с ожидаемым издателем.sub— уникальный в пределах этого издателя идентификатор пользователя, который никогда не переназначается. При работе с несколькими OP идентифицируйте пользователя по сочетаниюissиsub.aud— предполагаемые получатели. Убедитесь, что включёнclient_idклиента, и не принимайте дополнительных получателей, которым клиент не доверяет.exp— время истечения срока. Текущее время должно быть раньше него.iat— время выпуска. Политика клиента определяет допустимый возраст токена и другие ограничения.
Если в запросе аутентификации отправлялся nonce, требуйте наличия этого утверждения и проверяйте значение. Дополнительные утверждения или max_age также требуют соответствующих проверок.
Входные данные подписи RS256
Пусть H — защищённый заголовок в UTF-8, P — полезная нагрузка в UTF-8, а B64u — Base64url без заполнения. Подписывается следующая последовательность байт; || означает конкатенацию.
RS256 использует RSASSA-PKCS1-v1_5 с SHA-256. Хеширование является частью этого алгоритма подписи; формула не добавляет ещё один внешний хеш. Третья часть токена — B64u(S). Проверяйте подпись открытым ключом доверенного OP с заданным алгоритмом. Нельзя в общем случае описывать проверку подписи как расшифровку открытым ключом.
Непроверенный токен не должен определять разрешённые алгоритмы или источник ключей. Используйте доверенный JWKS из проверенной конфигурации или процедуры Discovery OP и обрабатывайте ротацию и кеширование ключей. kid служит для выбора внутри этого доверенного набора, а не для загрузки ключей по произвольному URL из токена.
Частичный пример проверки на Python
Функция PyJWT ниже поддерживает ограниченную конфигурацию: RS256, отсутствие шифрования, единственный получатель в виде строки и nonce, уже отправленный в запросе. Вызывающий код передаёт в signing_key открытый ключ из JWKS доверенного OP. Обязательные утверждения должны присутствовать и иметь допустимые значения. Если присутствует azp, в этой конфигурации оно должно совпадать с client_id.
Эта функция не завершает вход. Вызывающий код должен проверять и поддерживать связь обратного вызова с исходной транзакцией, state, обмен кода, ожидаемого издателя и однократное использование nonce. Несколько получателей, шифрование, дополнительные требования аутентификации, max_age и acr находятся вне рамок примера.
| |
Функция возвращает утверждения, проверенные в этих пределах. Не объявляйте аутентификацию успешной только по корректной подписи и не связывайте учётные записи по непроверенным имени или адресу электронной почты.
4. Вопросы безопасности
4.1 state, nonce, PKCE и CSRF
Если используется state, безопасно свяжите непредсказуемое значение с транзакцией, начатой в браузере. При обратном вызове проверьте значение, срок действия и то, что оно ещё не использовано, затем пометьте его использованным. Совпадение строк само по себе не предотвращает все виды CSRF.
RFC 9700 также описывает возможность полагаться на CSRF-защиту PKCE после подтверждения поддержки сервером и применение nonce OIDC. Эти меры по-прежнему требуют привязки к конкретной транзакции и корректной проверки. state, nonce и PKCE выполняют разные функции; постоянные значения или пропущенные сравнения не дают ожидаемой защиты. Несколько серверов авторизации также требуют защиты от mix-up.
4.2 Истечение срока и токены обновления
Короткий срок действия токена доступа помогает ограничить время использования украденного токена. Значение вроде 15 минут — пример проектного решения, а не универсальное требование. При проверке exp подписанного JWT условие проверки срока имеет вид:
Это проверка только срока действия, а не доказательство полной действительности токена или прав доступа. Также проверяйте алгоритм, ключ, издателя, получателей и, когда применимо, nbf, статус отзыва и связь с запросом аутентификации. Явно управляйте допустимым расхождением часов, если оно предусмотрено.
Выдача токенов обновления необязательна. Без них клиенту может потребоваться новая транзакция авторизации. Для токенов обновления публичных клиентов RFC 9700 требует привязки к отправителю (sender constraints) или ротации. Эта привязка криптографически связывает токен с предъявляющим его клиентом. Отзыв токена обновления не обязательно немедленно делает недействительными уже выданные токены доступа.
4.3 Выбор потока и реализации
Для веб-приложений, SPA и нативных приложений с участием пользователя исходите из кода авторизации с PKCE. Выбирайте конфигурацию с учётом типа клиента, хранения токенов, поддержки провайдера и модели угроз. Не навязывайте тот же поток другим задачам, например M2M с учётными данными клиента.
Заключение
Используйте токены доступа OAuth 2.0 для обращения к ресурсам, а ID-токены OIDC проверяйте как результаты аутентификации для клиента. Проверяйте привязку кода к PKCE, подпись, обязательные утверждения, nonce и связь обратного вызова с исходной транзакцией.
Источники
- RFC 6749: OAuth 2.0 (роли, токены и учётные данные клиента; обновлённые рекомендации безопасности см. в RFC 9700)
- RFC 7636: PKCE (раздел 4, раздел 7.1 и приложение B)
- RFC 9700: безопасность OAuth 2.0 (разделы 2.1, 2.2.2 и 2.4)
- OpenID Connect Core 1.0 (разделы 2, 3.1.3.7 и 5.7)
- RFC 7515: JWS (разделы 3.1 и 5.1)
- API PyJWT (разрешённые алгоритмы, обязательные утверждения и проверка получателя)
