Как работает OAuth 2.0 и OpenID Connect

Эта статья объясняет, как работает OAuth 2.0, его историю, особенности, и различия с OpenID Connect, а также рассматривает возможности единого входа (SSO).

· 14 минуты на чтение
Как работает OAuth 2.0 и OpenID Connect

В этой статье мы рассмотрим историю возникновения OAuth, его основные принципы работы и архитектуру. Поговорим о том, как протокол стал основой для безопасного взаимодействия между приложениями, не требующего передачи учётных данных. Кроме того, разберём ключевые различия между OAuth 2.0 и OpenID Connect (OIDC), чтобы понять, как OIDC дополняет возможности OAuth для обеспечения единой аутентификации (Single Sign-On, SSO). Мы также обсудим, какие сценарии применения лучше подходят для каждого из этих протоколов, и как обеспечить их безопасное использование в современных веб-приложениях.

Спонсор поста

История возникновения OAuth

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

В «ранние времена» интернета всё было намного проще и одновременно менее безопасно. Пользователи просто передавали логин и пароль от одного сервиса другому, чтобы тот мог войти в их учётную запись и получить доступ к необходимой информации. Например, на заре становления, Facebook предлагал пользователям ввести логин и пароль от их аккаунта Gmail, чтобы отправить приглашения их контактам. Такой подход имел серьёзный недостаток: передавая свои данные, пользователи предоставляли сервису полный доступ к своей учётной записи.

Для решения этой проблемы и был разработан стандарт OAuth. С его помощью вы можете разрешить приложению доступ к вашим данным или функциям другого приложения от вашего имени, не раскрывая свой пароль. OAuth 2.0 делает этот процесс удобным и безопасным, предоставляя следующие ключевые преимущества:

  • Повышенная безопасность. OAuth 2.0 позволяет предоставлять доступ к данным без необходимости передачи пароля, что защищает учётные данные пользователя от компрометации.
  • Ограничение прав доступа. OAuth предоставляет доступ только к необходимым данным и функциям. Например, приложение может получить доступ к контактам, но не к личным сообщениям или файлам пользователя.
  • Гибкость. OAuth 2.0 поддерживает различные типы разрешений, что позволяет адаптировать его под разные приложения (веб, мобильные и серверные) и сценарии доступа.
  • Централизованное управление доступом. OAuth 2.0 даёт владельцу ресурса возможность управлять списком приложений, которым предоставлен доступ, и отзывать доступ в любой момент.
  • Поддержка единого входа (SSO). OAuth 2.0 легко интегрируется с решениями SSO, такими как OpenID Connect, что позволяет пользователям использовать один аккаунт для доступа ко многим сервисам, упрощая процесс входа.
🙅‍♂️
OAuth 1.0 практически не используется, так как он был слишком сложным. Сегодня мы используем OAuth 2.0.

Основы Oauth 2.0

OAuth 2.0 основан на использовании базовых веб-технологий: HTTP-запросов, редиректов и т. п. Это делает его универсальным стандартом, который можно использовать на любой платформе с доступом к интернету и браузеру: на сайтах, в мобильных и десктопных приложениях, а также в браузерных плагинах.

Основные роли

Прежде чем рассмотреть сам процесс авторизации, важно понять основные роли, участвующие в схеме работы OAuth 2.0. Каждая из этих ролей выполняет свою задачу, обеспечивая безопасность и точность процесса доступа к данным.

Разделение на роли позволяет системе авторизации гибко контролировать доступ к защищённым ресурсам, минимизируя риски утечки информации. Рассмотрим эти роли на примере авторизации Facebook через Gmail:

  • Владелец ресурса. Это пользователь, который владеет данными (такими как контакты в Gmail). Именно он решает, кому и в каком объёме предоставить доступ к своим данным. В некоторых случаях владельцем ресурса может быть не пользователь, а другой сервер, например, при взаимодействии серверов между собой.
  • Клиент. Это устройство пользователя или сервис, которому требуется доступ к данным владельца ресурса. В нашем примере Facebook выступает клиентом, запрашивая доступ к контактам пользователя в Gmail для отправки приглашений.
  • Сервер ресурсов. Это сервер, где хранятся защищённые данные владельца ресурса. В данном примере сервером ресурсов является сервер Gmail, на котором лежат контакты пользователя.
  • Авторизационный сервер. Этот сервер отвечает за проверку прав доступа к данным. В нашем примере, авторизационный сервер – это сервер Google, который выполняет аутентификацию пользователя и выдаёт токен доступа (access token), подтверждающий разрешение на доступ к данным.
🤲
OAuth допускает объединение сервера ресурсов и авторизационного сервера, хотя в большинстве случаев они остаются раздельными.

Базовая схема протокола

Теперь, когда роли понятны, вернёмся к примеру с Facebook и Gmail. На анимации ниже схематично показано, как правильно реализовать этот процесс с помощью OAuth 2.0. Google использует свой авторизационный сервер для проверки прав доступа на всех своих сервисах, включая Gmail, который в данном случае лишь хранит ресурсы, но не участвует в процессе авторизации.

0:00
/0:13
Клиентом может быть как пользовательское устройство (например, мобильное приложение или браузер), так и сервер, которому нужно получить доступ к данным, расположенным на другом сервере.

Access Token

Ключевая цель процесса – предоставить клиенту (в нашем случае Facebook) доступ к данным, не раскрывая пароля. Клиент должен получить от авторизационного сервера специальный токен доступа (access token). Этот токен позволяет запрашивать данные у сервера ресурсов, но ограничивает доступ в соответствии с заданными разрешениями.

Особенности Access Token:

  • Ограниченное время жизни. Access token имеет ограниченный срок действия для повышения безопасности.
  • Возможность отзыва. Если есть подозрение, что токен скомпрометирован, его можно отозвать, заблокировав дальнейший доступ.
  • Отдельность от пароля. Компрометация токена не означает утечку пароля, так как из токена невозможно получить логин и пароль пользователя.
  • Ограниченные права доступа (scope). Access token может предоставить доступ только к определённым данным. Например, Facebook может запросить доступ только к списку контактов, но не к сообщениям.

Refresh Token

Помимо access token, авторизационный сервер может выдать клиенту refresh token. Когда срок действия access token истекает, клиент отправляет запрос к авторизационному серверу, передавая refresh token. Авторизационный сервер проверяет его действительность и, если всё в порядке, выдаёт новый access token. Так сессия пользователя остаётся активной без повторной аутентификации.

Поскольку refresh token даёт возможность запрашивать новые access token, его компрометация — серьёзная угроза. При утечке злоумышленник получает долгосрочный доступ к данным. Поэтому refresh token хранят в защищённой среде, особенно в серверных приложениях.

Для публичных клиентов (SPA, мобильные приложения) RFC 9700 требует дополнительной защиты: либо привязки токена к отправителю (sender-constrained), либо ротации refresh token. При ротации сервер при каждом обращении выдаёт новый refresh token и инвалидирует старый. Если старый токен предъявлен повторно — это сигнал компрометации: сервер должен отозвать всю цепочку токенов для данной сессии. Альтернативы sender-constrained — DPoP (RFC 9449) и mTLS: они криптографически привязывают токен к конкретному клиентскому ключу, и украденный токен без соответствующего приватного ключа становится бесполезным.

Способы получения Access Token

Существует четыре основных способа получения access token в OAuth 2.0, каждый из которых подходит для определённых сценариев:

  • Authorization Code Grant. Это самый безопасный и надёжный метод. Он предполагает обмен одноразового кода авторизации на access token через серверную часть приложения, что делает его менее уязвимым к перехвату токена.
  • Implicit Grant. Более простой метод, используемый для клиентских приложений без серверной части, таких как SPA (Single Page Applications). access token передаётся напрямую через URL-фрагмент, что упрощает реализацию, но снижает безопасность, так как токен может быть доступен через JavaScript.
  • Resource Owner Password Credentials. В этом методе пользователь передаёт свои логин и пароль напрямую клиенту, который затем запрашивает access token у сервера авторизации. Он подходит только для доверенных клиентов, так как пользователь делится своими учётными данными с клиентом, что создаёт риск утечки.
  • Client Credentials. Используется для межсерверного взаимодействия, когда одно серверное приложение (клиент) запрашивает access token у авторизационного сервера с использованием client_id и client_secret. Этот метод подходит для сценариев, когда доступ к ресурсам необходим от имени самого клиента, а не пользователя.

Подробно рассмотрим каждый способ.

Client Credentials

Рассмотрим один из самых простых способов авторизации в OAuth 2.0 — схему Client Credentials. Этот метод разработан для безопасного взаимодействия между сервисами и часто используется, когда один сервис должен обращаться к другому сервису без участия пользователя.

Каждый клиент должен быть зарегистрирован на авторизационном сервере. В процессе регистрации клиент получает уникальные идентификаторы:

  • client_id – это публичный идентификатор клиента, который передаётся в каждом запросе на авторизацию. Он позволяет авторизационному серверу определить, какое приложение запрашивает доступ.
  • client_secret – это секретный ключ, который используется для проверки подлинности клиента. Он должен храниться в надёжном месте и не должен быть раскрыт третьим лицам.

Запрос токена (1). Service 1 отправляет запрос к авторизационному серверу, передавая свои client_id и client_secret. Это делается с использованием метода POST и указанием типа авторизации grant_type=client_credentials:

curl --request POST \
 --url 'https://YOUR_DOMAIN/oauth/token' \
 --header 'content-type: application/x-www-form-urlencoded' \
 --data grant_type=client_credentials \
 --data client_id=YOUR_CLIENT_ID \
 --data client_secret=YOUR_CLIENT_SECRET \
 --data audience=YOUR_API_IDENTIFIER

Здесь audience указывает целевой ресурс (Service 2), для которого будет действовать access_token. Это поле определяет, какие именно ресурсы будут доступны по этому токену. Авторизационный сервер проверяет audience при выдаче токена, чтобы убедиться, что запрос направлен на доступ к конкретному API. Это помогает избежать использования токена не по назначению и усиливает безопасность.

Получение access_token (2). В ответ на запрос авторизационный сервер выдаёт access_token, который Service 1 может использовать для обращения к Service 2:

{
    "access_token": "eyJz93a...k4laUWw",
    "token_type": "Bearer",
    "expires_in": 86400
}
  • access_token: строка токена, которая используется для авторизации запросов к Service 2. Это временный токен доступа.
  • token_type: обычно Bearer, что указывает на способ передачи токена в заголовке Authorization.
  • expires_in: время действия токена в секундах (например, 86400 секунд, что соответствует 24 часам). После этого срока действия токен становится недействительным.

Обращение к Service 2 (3). Service 1 использует полученный access_token для отправки запросов к Service 2. Токен передаётся в заголовке Authorization с типом Bearer:

curl --request GET \
--url https://api2.com/api \
--header 'authorization: Bearer ACCESS_TOKEN' \
--header 'content-type: application/json'

Проверка токена (4). Получив запрос с access_token, Service 2 должен убедиться, что токен действителен и не был отозван. Для этого существуют два подхода:

  • Запрос к авторизационному серверу: Service 2 может обратиться к авторизационному серверу с помощью протокола, описанного в RFC 7662, чтобы проверить действительность токена. Авторизационный сервер может подтвердить, что токен не истёк и не был отозван.
  • Локальная проверка с использованием JWT: Если access_token представлен в виде JWT (JSON Web Token), Service 2 может выполнить локальную проверку токена. JWT содержит в себе закодированную информацию о клиенте и правах доступа, и Service 2 может проверить подпись токена, чтобы убедиться в его подлинности без обращения к авторизационному серверу. Это снижает нагрузку на сервер и ускоряет обработку запросов.
Что такое JWT токен?
Эта статья посвящена детальному разбору JWT и его возможностей. Мы изучим структуру токена и построим его с нуля. Затем рассмотрим наиболее распространенные способы использования.

Когда использовать Client Credentials?

Схема Client Credentials подходит для следующих сценариев:

  • Межсерверное взаимодействие, где один сервер должен получить доступ к API другого сервера, например, для синхронизации данных.
  • Микросервисная архитектура, где отдельные микросервисы взаимодействуют друг с другом через API.
  • Сервисные учётные записи, когда доступ к API требуется не от имени конкретного пользователя, а от имени самого сервиса.

Важно учитывать, что client_secret должен храниться только в безопасных серверных окружениях. Это значит, что клиент должен быть доверительным и иметь возможность безопасно хранить client_secret без риска его утечки. Поэтому Client Credentials не рекомендуется использовать в клиентских приложениях, таких как мобильные или SPA, где client_secret может быть легко извлечён.

Resource Owner Password Credentials

⚠️
Этот грант запрещён. RFC 9700 (Section 2.4) прямо указывает: «The resource owner password credentials grant MUST NOT be used». Он отсутствует в OAuth 2.1 и структурно несовместим с MFA, passkeys и современными схемами аутентификации. Замена — Authorization Code + PKCE.

Тем не менее схема встречается в legacy-системах, поэтому разберём, как она устроена — чтобы понимать, что именно перед нами, когда видим её в чужом коде.

Принцип прост до опасности: пользователь передаёт логин и пароль напрямую клиентскому приложению, клиент отправляет их на авторизационный сервер и получает токен. Авторизационный сервер здесь лишь посредник, но пользователь вынужден доверять клиенту свои учётные данные.

Запрос токена выглядит так:

curl -X POST https://auth.example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=user@example.com" \
  -d "password=secret" \
  -d "client_id=my-app" \
  -d "scope=read"

Сервер возвращает стандартный ответ:

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8"
}

Проблема не в реализации, а в модели угроз. Клиент получает пароль — а значит, может его сохранить, передать куда угодно или скомпрометировать при утечке. Схема структурно несовместима с любым вторым фактором: нет редиректа, нет интерактивного шага, нет точки входа для MFA. Именно поэтому RFC 9700 не рекомендует, а запрещает этот грант.

Спонсор поста 3

Authorization Code Grant

Authorization Code Grant — рекомендуемый способ авторизации в OAuth 2.0, особенно для серверных приложений. Ключевое свойство: access token никогда не проходит через браузер. Пользователь получает только короткоживущий code, а обмен на токены происходит напрямую между сервером приложения и авторизационным сервером.

Flow разворачивается в пять шагов:

  • Клиент перенаправляет пользователя на авторизационный сервер с параметрами response_type=code, client_id, redirect_uri, scope и state.
  • Пользователь проходит аутентификацию и даёт согласие.
  • Авторизационный сервер перенаправляет обратно на redirect_uri с параметрами code и state.
  • Клиент проверяет state и обменивает code на токены — server-to-server запросом с client_secret.
  • Авторизационный сервер возвращает access_token и, опционально, refresh_token.

Параметр state — одноразовый CSRF-токен. Клиент генерирует случайное значение перед редиректом, сохраняет в сессии и при получении callback проверяет совпадение. Несовпадение означает, что callback пришёл не в ответ на наш запрос — возможна CSRF-атака или подмена редиректа.

Запрос авторизации:

GET https://auth.example.com/oauth/authorize
  ?response_type=code
  &client_id=my-app
  &redirect_uri=https://app.example.com/callback
  &scope=read%20write
  &state=xK92mP1qZ7

После согласия пользователя браузер редиректит на:

https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=xK92mP1qZ7

Клиент сверяет state, затем обменивает code:

curl -X POST https://auth.example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=SplxlOBeZQQYbYS6WxSbIA" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "client_id=my-app" \
  -d "client_secret=s3cr3t"

В ответ приходят токены:

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8"
}

PKCE — Proof Key for Code Exchange

Authorization Code Grant с client_secret хорош для серверных приложений — они могут хранить секрет в безопасной среде. Мобильные приложения и SPA так не могут: любой секрет, зашитый в клиентский код, считается скомпрометированным по определению. Для них существует PKCE (RFC 7636). Согласно RFC 9700, PKCE обязателен для публичных клиентов и рекомендован для конфиденциальных.

Вместо статического client_secret клиент генерирует одноразовую пару значений:

  • code_verifier — случайная строка из символов [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" длиной 43–128 символов
  • code_challenge = BASE64URL(SHA256(code_verifier)) с методом S256

В authorization request добавляются code_challenge и code_challenge_method=S256. В token request вместо секрета передаётся code_verifier. Сервер вычисляет хэш от полученного верификатора и сравнивает с code_challenge, который он сохранил при выдаче code.

Пример пары значений из RFC 7636:

code_verifier:  dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

Запрос авторизации с PKCE:

GET https://auth.example.com/oauth/authorize
  ?response_type=code
  &client_id=my-spa
  &redirect_uri=https://app.example.com/callback
  &scope=read
  &state=xK92mP1qZ7
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Обмен кода на токен — без client_secret, но с code_verifier:

curl -X POST https://auth.example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=SplxlOBeZQQYbYS6WxSbIA" \
  -d "redirect_uri=https://app.example.com/callback" \
  -d "client_id=my-spa" \
  -d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

code_verifier хранится в памяти JS-приложения на время flow — он одноразовый и живёт до завершения обмена. Перехват code без знания code_verifier бесполезен: без него токен не получить.

Implicit Grant

⚠️
Implicit Grant запрещён. RFC 9700 исключает его из допустимых грантов, OAuth 2.1 его не содержит. Для SPA и мобильных приложений без серверной части — Authorization Code + PKCE.

Implicit Grant появился как упрощение для браузерных приложений: вместо двухшагового flow авторизационный сервер возвращал access_token прямо в URL-фрагменте (#access_token=...), JavaScript читал его из window.location.hash без каких-либо серверных вызовов.

URL-фрагмент — принципиально небезопасное место для токена. Он оседает в истории браузера, попадает в Referer-заголовки при переходе на внешние ресурсы и в логи прокси-серверов. XSS-уязвимость на любой странице домена даёт атакующему прямой доступ к токену. При этом нет механизма подтверждения получателя: client_secret у публичного клиента нет, и сервер не может убедиться, что токен получил именно ожидаемый клиент, а не перехватчик с подменённым redirect_uri.

Authorization Code + PKCE устраняет все эти проблемы без серверной инфраструктуры. code_verifier живёт в памяти приложения, client_secret не нужен, токен никогда не появляется в URL. Для любого публичного клиента это единственный путь, соответствующий актуальным требованиям безопасности.

Device Authorization Grant

Представьте телевизор, которому нужно войти в аккаунт Netflix. Нет браузера, нет нормальной клавиатуры — только пульт. Именно для таких устройств появился Device Authorization Grant, описанный в RFC 8628.

Грант решает одну конкретную задачу: аутентифицировать устройство с ограниченным вводом или вообще без браузера. Сюда попадают Smart TV, IoT-устройства, игровые консоли и CLI-утилиты — например, GitHub CLI (gh auth login) и Google Cloud SDK (gcloud auth login).

Flow делится на две независимые ветки: устройство общается с сервером по polling, а пользователь параллельно подтверждает доступ со своего телефона или ноутбука.

Шаг 1. Устройство запрашивает код авторизации:

curl -X POST https://YOUR_DOMAIN/oauth/device/code \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'client_id=YOUR_CLIENT_ID&scope=openid%20profile'

Сервер отвечает сразу несколькими значениями:

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://YOUR_DOMAIN/activate",
  "verification_uri_complete": "https://YOUR_DOMAIN/activate?user_code=WDJB-MJHT",
  "expires_in": 900,
  "interval": 5
}

user_code — короткий код, который устройство показывает на экране. RFC 8628 не фиксирует его длину: сервер выбирает её сам, балансируя между удобством ввода и энтропией. verification_uri_complete содержит уже вписанный код — удобно для QR-кодов на Smart TV, чтобы пользователь не вводил символы вручную.

Пользователь открывает verification_uri на своём устройстве, вводит user_code и подтверждает запрос. Устройство в это время ничего не знает о том, что происходит на стороне пользователя, и просто поллит token endpoint с заданным интервалом:

curl -X POST https://YOUR_DOMAIN/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:device_code
      &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
      &client_id=YOUR_CLIENT_ID'

Пока пользователь не подтвердил запрос, сервер возвращает одно из четырёх состояний:

  • authorization_pending — пользователь ещё не ответил, продолжать polling;
  • slow_down — устройство опрашивает слишком часто, нужно увеличить интервал на 5 секунд;
  • access_denied — пользователь явно отклонил запрос;
  • expired_tokendevice_code истёк, нужно начинать flow заново.

Как только пользователь подтверждает доступ, следующий polling-запрос возвращает полноценный access_token.

Грант подходит только тогда, когда на устройстве действительно нет возможности открыть браузер. Если браузер есть — используйте Authorization Code + PKCE: он не требует polling и не держит device_code живым несколько минут.

OpenID Connect

OAuth 2.0 решает задачу авторизации — приложение получает доступ к ресурсам от имени пользователя. Но сам протокол не отвечает на вопрос «кто этот пользователь?». Это принципиальное разграничение: авторизация говорит «ты можешь», аутентификация говорит «ты — это ты».

OpenID Connect (OIDC) — надстройка над OAuth 2.0, которая добавляет уровень аутентификации. Технически это тот же Authorization Code Flow, но с дополнительным токеном — id_token. На основе OIDC строятся системы единого входа (SSO).

Различие между двумя токенами критично:

  • id_token — JWT с данными о пользователе. Его назначение — подтвердить личность: клиентское приложение читает токен, проверяет подпись и узнаёт, кто вошёл. Этот токен предназначен только для клиента, не для API.
  • access_token — токен для доступа к защищённым ресурсам. Его передают в заголовке Authorization при вызовах API.

Распространённая ошибка — передавать id_token в API-запросах вместо access_token. Сервер ресурсов не должен принимать id_token: он не предназначен для этой роли и не несёт нужной аудитории (aud).

JWT (JSON Web Token) — подписанный JSON-объект в формате header.payload.signature, закодированный в Base64URL. Подпись позволяет получателю проверить токен без обращения к серверу.

Поток OpenID Connect

Поток OIDC строится поверх Authorization Code Flow. Отличие начинается с первого запроса: клиент добавляет scope=openid. Именно это значение сигнализирует серверу авторизации, что нужен id_token. Без openid в scope id_token не возвращается.

В ответе на обмен кода токены приходят вместе:

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiJ9...",
  "refresh_token": "8xLOxBtZp8"
}

id_token — это JWT. После декодирования payload выглядит так:

{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "aud": "s6BhdRkqt3",
  "exp": 1311281970,
  "iat": 1311280970,
  "name": "Jane Doe",
  "email": "jane@example.com"
}

Первые пять claims — обязательные по спецификации. iss + sub вместе образуют глобально уникальный идентификатор пользователя. aud должен совпадать с client_id приложения — это защита от того, чтобы токен, выданный одному клиенту, не приняло другое приложение. В Implicit Flow к обязательным добавляется nonce — одноразовое значение для защиты от replay-атак.

Клиент проверяет подпись id_token самостоятельно, используя публичный ключ провайдера. Это устраняет необходимость дополнительного сетевого запроса для валидации.

Scopes и claims

OIDC стандартизирует набор scope-значений, каждое из которых открывает определённую группу claims:

ScopeДобавляемые claims
openidОбязательный. Только sub
profilename, given_name, family_name, picture, locale
emailemail, email_verified
addressaddress (структурированный объект)
phonephone_number, phone_number_verified

Если нужны только базовые данные для SSO — достаточно openid. Полный профиль можно запросить через UserInfo Endpoint (GET /userinfo с access_token), не перегружая id_token.

Discovery Endpoint

OIDC-провайдеры публикуют метаданные по стандартному адресу:

GET {issuer}/.well-known/openid-configuration

Например: https://accounts.google.com/.well-known/openid-configuration

Ответ — JSON с полным описанием возможностей провайдера. Ключевые поля:

  • issuer — базовый URL провайдера
  • authorization_endpoint — адрес для старта авторизации
  • token_endpoint — где обменивают код на токены
  • userinfo_endpoint — endpoint для получения данных пользователя
  • jwks_uri — публичные ключи для проверки подписи токенов
  • scopes_supported — список поддерживаемых scope

Клиентские библиотеки используют Discovery для автоконфигурации: достаточно указать issuer, остальное они находят сами.

Заключение

OAuth 2.0 и OpenID Connect решают разные задачи, но работают как единая система: OAuth 2.0 управляет доступом к ресурсам, OIDC поверх него добавляет подтверждение личности. Вместе они покрывают большинство сценариев — от делегированного API-доступа до SSO в корпоративных системах. Главное не путать роли токенов: access_token идёт в API, id_token остаётся у клиента.

Стандарт продолжает развиваться. OAuth 2.1 (пока в статусе draft) консолидирует накопленный опыт безопасности: убирает deprecated grants (implicit, resource owner password), делает PKCE обязательным для всех публичных клиентов и ужесточает требования к redirect URI. DPoP (RFC 9449) привязывает токен к криптографическому ключу клиента, что лишает смысла кражу bearer-токена. PAR (RFC 9126) передаёт параметры авторизационного запроса через back-channel ещё до редиректа пользователя, закрывая класс атак на front-channel.

Дополнительные материалы

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇