Все уроки
БезопасностьПродвинутый14 мин чтенияВ моём стеке

OAuth 2.x и OpenID Connect

Делегированный доступ и «Войти через…» — с PKCE, короткоживущими токенами и правильными scopes.

  • OAuth 2.1
  • OIDC
  • PKCE
  • Tokens
  • Scopes
Шаг 1/6
Вы нажимаете «Sign in with ID» на app.com. Приложению ваш пароль видеть не нужно — в этом весь смысл OAuth.

За 30 секунд

OAuth позволяет приложению действовать от имени пользователя, ни разу не увидев его пароля: пользователь одобряет доступ на сервере авторизации, а приложение получает ограниченный access token с истекающим сроком. OpenID Connect добавляет поверх этого идентификацию, так что приложение ещё и узнаёт, кто пользователь, — именно на этом работает «Войти через Google» или Apple. Современный рецепт — authorization code flow с PKCE для любого клиента.

Почему я это использую

  • Я использую OAuth, потому что почти каждый API, который я делаю, обслуживает несколько клиентов — веб, мобильное приложение, админку, — а токены со scopes и сроком жизни — самый чистый способ их аутентифицировать, не передавая пароли.
  • В потребительских продуктах я использую социальный вход (Google, Apple, LinkedIn, Facebook), потому что он убирает трение при регистрации, а OpenID Connect — это стандарт, который делает его безопасным при правильной проверке.
  • Я внимательно отношусь к деталям — PKCE, срокам жизни токенов, отзыву, scopes, — потому что именно баги в аутентификации приводят к утечке аккаунтов, а предотвратить их на этапе проектирования дёшево.
  • Знание спецификаций позволяет мне для каждого проекта осознанно выбирать между Laravel Passport, Sanctum, управляемым identity provider или собственными JWT, а не хвататься по умолчанию за один инструмент.

Как это работает

01Идея: ключ для парковщика, а не ключ от дома

У некоторых машин есть valet key — ключ для парковщика: он заводит двигатель, но не открывает багажник. OAuth выдаёт приложениям такие ключи. Участвуют четыре роли. Resource owner — пользователь. Client — приложение, которому нужен доступ. Authorization server аутентифицирует пользователя и выдаёт токены. Resource server — это API, который принимает токены. Пользователь входит только на сервере авторизации и одобряет конкретные scopes — «читать ваш календарь», — а клиент получает access token, действующий для этих scopes и ограниченное время. Клиент никогда не видит пароль. Важное различие: OAuth 2.0 — это про авторизацию (что приложению разрешено делать), а не про аутентификацию (кто пользователь). Использовать голый OAuth access token как доказательство личности — классическая ошибка; ровно этот пробел и закрывает OpenID Connect.

  • Resource owner: пользователь
  • Client: веб-, мобильное или серверное приложение
  • Authorization server: выполняет вход пользователя, выдаёт токены
  • Resource server: ваш API, проверяет токены

02Authorization code flow с PKCE, шаг за шагом

Это рекомендуемый flow для веб-приложений, SPA и мобильных приложений. Клиент создаёт случайный code_verifier и отправляет его SHA-256-хэш (code_challenge) в запросе авторизации. Пользователь входит и даёт согласие, а сервер делает редирект обратно с одноразовым authorization code. Клиент по back channel обменивает код плюс исходный verifier на токены. PKCE (Proof Key for Code Exchange, RFC 7636) означает, что украденный код бесполезен без verifier, который никогда не покидал приложение. Параметр state защищает от CSRF на редиректе, а в OIDC nonce привязывает ID token к этому входу. Redirect URI должны быть зарегистрированы и совпадать точно. OAuth 2.1, на конец 2026 года всё ещё черновик IETF, но уже отражённый в RFC 9700 (OAuth 2.0 Security Best Current Practice), требует PKCE для всех клиентов и убирает implicit и password grants.

http
# 1) Browser is redirected to the authorisation server
GET https://auth.example.com/authorize?response_type=code
  &client_id=web-app
  &redirect_uri=https://app.example.com/callback
  &scope=openid%20profile%20email%20orders:read
  &state=af0ifjsldkj&nonce=n-0S6_WzA2Mj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
 
# 2) Back channel: exchange the code for tokens
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded
 
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https://app.example.com/callback
&client_id=web-app&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

03Токены: access, refresh и ID token

Access token — это то, что клиент отправляет в API, обычно как «Authorization: Bearer ...». Он должен жить недолго — минуты, а не месяцы, — потому что bearer-токеном может воспользоваться любой, кто им владеет. Он может быть непрозрачным (opaque — API проверяет его у сервера авторизации через introspection) или JWT (API проверяет подпись локально). Refresh token позволяет клиенту получать новые access token без повторного входа пользователя; он долгоживущий и мощный, поэтому храните его бережно, ротируйте при каждом использовании и отзывайте при выходе. ID token из OpenID Connect — это подписанный JWT, который сообщает клиенту, кто вошёл: issuer (iss), идентификатор субъекта (sub), audience (aud), срок действия (exp), nonce и при желании email или имя. ID token предназначен для чтения клиентом; его не нужно отправлять в ваши API вместо access token.

  • Access token: для API, короткоживущий, со scopes
  • Refresh token: для клиента, чтобы обновлять доступ; ротируйте и отзывайте
  • ID token: для клиента, доказывает, кто вошёл

04OpenID Connect: «Войти через Google / Apple»

OpenID Connect (OIDC) — тонкий слой идентификации поверх OAuth 2.0. Вы добавляете scope «openid», и сервер авторизации возвращает ID token вместе с access token. Провайдеры публикуют discovery-документ по адресу /.well-known/openid-configuration, где перечислены их эндпоинты и URL JWKS с ключами подписи. Чтобы безопасно залогинить пользователя, бэкенд должен проверить ID token: подпись текущим ключом провайдера, iss равен провайдеру, aud равен вашему client id, exp не истёк, а nonce совпадает с отправленным. Затем привязывайте аккаунт по стабильному sub провайдера, а не по email, потому что email меняются и не всегда подтверждены. На мобильных устройствах нативные SDK (Google Sign-In, Sign in with Apple) возвращают ID token или authorization code, который приложение отправляет на бэкенд для той же проверки, прежде чем вы выдадите собственную сессию или API-токены.

php
use Laravel\Socialite\Facades\Socialite;
 
// Redirect the user to Google
Route::get('/auth/google', fn () => Socialite::driver('google')->redirect());
 
// Callback: Socialite exchanges the code and fetches the profile
Route::get('/auth/google/callback', function () {
    $g = Socialite::driver('google')->user();
 
    $user = User::firstOrCreate(
        ['google_id' => $g->getId()],          // stable provider "sub"
        ['name' => $g->getName(), 'email' => $g->getEmail()],
    );
    Auth::login($user);
    return redirect('/account');
});

05Другие grants и API-ключи

Не у каждого клиента есть пользователь в браузере. Client credentials: бэкенд-сервис аутентифицируется сам за себя (client id и secret, а лучше подписанный assertion или mutual TLS), чтобы вызвать другой сервис, — machine-to-machine. Device authorization (RFC 8628): смарт-телевизоры и CLI показывают код, который пользователь вводит на телефоне. Token exchange (RFC 8693): сервис обменивает один токен на другой с более узким scope или другой audience. Refresh token grant: обновление доступа. Устаревшие: implicit grant (токены во фрагменте URL) и resource owner password grant (приложение само собирает пароль пользователя) — оба убраны в OAuth 2.1. Простые API-ключи по-прежнему распространены в интеграциях server-to-server; если вы их используете, делайте их со scopes, с префиксом окружения (live/test), храните в виде хэша, ограничивайте по частоте и обеспечьте ротацию без простоя.

06Laravel Passport на практике

Laravel Passport — полноценный OAuth2-сервер для Laravel API, построенный на League OAuth2 server. Актуальные версии поддерживают authorization code grant (с PKCE для публичных клиентов), client credentials, device authorization, refresh tokens и personal access tokens; password и implicit grants отключены, если не включить их явно. Scopes задаются через Passport::tokensCan, роуты защищаются guard auth:api плюс middleware для scope, а сроки жизни токенов настраиваются в service provider — по умолчанию access token живёт год, что для большинства приложений слишком долго, так что сокращайте. Для first-party SPA на том же домене сессии Laravel Sanctum на cookies часто проще OAuth; Passport хорош, когда нужны настоящие OAuth-клиенты, доступ третьих сторон или мобильные приложения на токенах.

php
// AppServiceProvider::boot()
use Carbon\CarbonInterval;
use Laravel\Passport\Passport;
 
Passport::tokensCan([
    'orders:read'   => 'View your orders',
    'orders:create' => 'Place orders',
]);
Passport::tokensExpireIn(CarbonInterval::minutes(30));
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
 
// routes/api.php
use Laravel\Passport\Http\Middleware\CheckToken;
 
Route::get('/orders', [OrderController::class, 'index'])
    ->middleware(['auth:api', CheckToken::using('orders:read')]);

Когда применять

  • API, которым пользуются несколько собственных клиентов (веб, мобильное приложение, админка), и им нужны токены со scopes и сроком жизни.
  • Доступ сторонних приложений или партнёров к данным пользователя с его согласия.
  • Социальный или корпоративный вход (Google, Apple, Microsoft, LinkedIn, корпоративный SSO) через OpenID Connect.
  • Machine-to-machine вызовы между сервисами через client credentials grant.

Когда не стоит

  • Одно собственное веб-приложение на одном домене: защищённые HTTP-only сессионные cookies (например, SPA-режим Laravel Sanctum) проще и так же безопасны.
  • Внутренние скрипты, которым нужен лишь узкий отзываемый API-ключ, — полноценный OAuth-сервер тут добавит сложности без пользы.
  • Когда вы собираетесь писать собственный OAuth-сервер с нуля — вместо этого используйте поддерживаемую библиотеку или identity provider.

Альтернативы

  • Сессионные cookies

    Классические серверные сессии с cookies HttpOnly, Secure, SameSite; идеальны для same-site веб-приложений.

  • Laravel Sanctum

    Лёгкая cookie-аутентификация для SPA и простые персональные API-токены для Laravel без полноценных OAuth-флоу.

  • Управляемые identity providers

    Auth0, Okta, Microsoft Entra ID, AWS Cognito, Keycloak (self-hosted): OAuth/OIDC, MFA и SSO как сервис.

  • SAML 2.0

    Более старый корпоративный стандарт SSO на XML, который до сих пор требуют некоторые корпоративные клиенты.

Лучшие практики

  1. Используйте authorization code flow с PKCE (S256) для каждого клиента, включая конфиденциальные веб-приложения.
  2. Регистрируйте точные redirect URI; никогда не допускайте wildcard-ов или open redirect.
  3. Делайте access token короткоживущими (минуты), ротируйте refresh token при использовании и отзывайте их при выходе и смене пароля.
  4. Проверяйте JWT полностью: подпись алгоритмом из allow-list, iss, aud, exp, nbf — и nonce для ID token.
  5. Проектируйте scopes вокруг бизнес-возможностей (orders:read) и проверяйте их на каждом эндпоинте, плюс авторизация на уровне объектов.
  6. Привязывайте социальные аккаунты по claim sub провайдера, а не только по email.
  7. Храните токены безопасно: в вебе — HttpOnly cookies или backend-for-frontend, на мобильных — Keychain/Keystore; никогда не храните долгоживущие токены в localStorage.

Частые ошибки

  • Использовать OAuth access token как доказательство входа без проверки OIDC или принимать ID token, выпущенные для другого приложения (без проверки aud).
  • Принимать JWT с alg «none» или позволять заголовку токена выбирать алгоритм.
  • Access token сроком на год без возможности отзыва.
  • Пропускать параметр state, оставляя возможность login CSRF на callback.
  • Зашивать client secret в бандл мобильного приложения или SPA, откуда его может извлечь кто угодно.

Глубже

07Правильная проверка JWT (и классические атаки)

JWT — это header.payload.signature в кодировке base64url. Проверка должна быть строгой. Зафиксируйте ожидаемый алгоритм в коде (например, RS256 или ES256) и отклоняйте всё остальное — исторические уязвимости возникали из-за библиотек, доверявших alg из заголовка, что позволяло «none» или путаницу HS256 с публичным ключом. Получайте ключи подписи с JWKS-эндпоинта издателя, кэшируйте их, выбирайте по kid и обновляйте при неизвестном kid, чтобы поддерживать ротацию ключей. Проверяйте iss и aud точно, exp и nbf с небольшим допуском на рассинхрон часов, а для ID token — nonce. Не кладите чувствительные данные в payload — он подписан, но не зашифрован. Эталонный документ — RFC 8725 (JWT Best Current Practices). Предпочитайте асимметричную подпись, чтобы у resource servers были только публичные ключи, а выпускать токены мог только сервер авторизации.

ts
import { createRemoteJWKSet, jwtVerify } from "jose";
 
const JWKS = createRemoteJWKSet(new URL("https://auth.example.com/.well-known/jwks.json"));
 
export async function verifyAccessToken(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: "https://auth.example.com",
    audience: "https://api.example.com",
    algorithms: ["RS256"],      // never trust the header's alg
    clockTolerance: 30,         // seconds
  });
  if (await denylist.has(payload.jti as string)) throw new Error("revoked");
  return payload; // sub, scope, exp, ...
}

08Ротация refresh token и обнаружение повторного использования

Refresh token — это драгоценности короны: они долгоживущие и могут выпускать access token. Ротация означает, что каждое обновление возвращает новый refresh token и делает старый недействительным. Обнаружение повторного использования добавляет ключевое свойство безопасности: если старый, уже использованный refresh token предъявлен снова, значит, у легитимного клиента или у атакующего есть украденная копия, — поэтому сервер отзывает всё семейство токенов и заставляет войти заново. Реализуйте это, храня для каждого refresh token id семейства и флаг использования. Добавьте абсолютный срок жизни (например, 30–90 дней) и таймаут простоя, чтобы брошенные сессии умирали. Обрабатывайте гонки в мобильных приложениях, где два параллельных запроса одновременно пытаются обновить токен: сериализуйте обновления на клиенте или разрешите очень короткое окно для предыдущего токена. Sender-constrained токены идут дальше: они привязывают токен к ключу клиента, так что украденный токен нельзя воспроизвести в другом месте.

09Sender-constrained токены: DPoP и mTLS

Bearer-токены работают как наличные: кто держит, тот и тратит. Sender-constrained токены работают как карта с PIN-кодом. В DPoP (RFC 9449) клиент генерирует пару ключей и с каждым запросом отправляет короткий подписанный proof JWT с HTTP-методом, URL, временной меткой и хэшем access token. Сервер авторизации привязывает токен к публичному ключу клиента (claim cnf), а API проверяет, что каждый запрос подписан тем же ключом. Токен, утёкший через логи или прокси, становится бесполезным. Mutual TLS (RFC 8705) достигает того же, привязывая токены к клиентскому сертификату, — это распространено в банковских и open-finance профилях вроде FAPI 2.0. Pushed Authorization Requests (PAR, RFC 9126) дополняют это, отправляя параметры авторизации по back channel, а не через URL в браузере, что предотвращает их подмену.

http
POST /orders HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IjEifQ...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Li4ufX0...
 
# DPoP proof payload (signed with the client's private key):
# { "htm": "POST", "htu": "https://api.example.com/orders",
#   "iat": 1793000000, "jti": "e1j3V_bKic8-LAEB", "ath": "<sha256 of access token>" }

10Идентификация в multi-tenant и B2B

В SaaS идентификация усложняется: пользователи принадлежат организациям, иногда нескольким, а корпоративные клиенты хотят свой SSO. Кладите тенанта в токен осознанно — claim tenant или org, выбранный при входе или через переключатель организаций, — и на каждом запросе проверяйте, что ресурс принадлежит этому тенанту; никогда не берите tenant id только из тела запроса. Храните роли и права в своём приложении, при необходимости маппя их из групп IdP, а не доверяйте произвольным claims от внешних IdP. Поддерживайте OIDC- или SAML-подключения для каждого тенанта для корпоративного SSO, плюс SCIM для провиженинга пользователей, если клиенты просят. Продумайте имперсонацию: сотрудники поддержки должны получать отдельный ограниченный по времени токен с claim act (actor), обязательной причиной и полным аудит-логом — и никогда не учётные данные самого клиента.

Реальные ситуации

01

Добавление Sign in with Apple в мобильное приложение

Проблема

В приложении на React Native уже есть вход по email и паролю. Команде нужно добавить Sign in with Apple и Google, и она хочет, чтобы аккаунты правильно связывались, когда один человек использует несколько способов.

Как решить

Используйте нативные SDK на устройстве, чтобы получить ID token или authorization code, затем отправьте его на бэкенд. Бэкенд проверяет подпись ID token по JWKS провайдера, а также iss, aud, равный client id вашего приложения, exp и nonce. Храните отдельную таблицу identities (provider, sub, user_id) и связывайте по sub. Apple может скрыть настоящий email за relay-адресом и передаёт имя только при первом входе, поэтому сохраните его в этот момент. Привязывайте к существующему аккаунту по email, только если провайдер пометил его как подтверждённый и пользователь согласился. Наконец, выдайте собственный короткоживущий API-токен плюс refresh token.

02

Access token утёк в логи

Проблема

На security review в логах приложения нашлись полные заголовки Authorization. Access token действуют год, а часть логов передавалась подрядчику.

Как решить

Относитесь к этому как к инциденту. Отзовите все access и refresh token, выданные до исправления, заставив пользователей войти заново, и ротируйте client secrets, которые тоже могли утечь. Уберите заголовки из логов: добавьте редактирование в logging middleware и в прокси и по возможности удалите старые логи. Сократите срок жизни access token до 15–30 минут и включите ротацию refresh token. Добавьте проверку denylist по jti для экстренного отзыва. Добавьте тест, который падает, если заголовки Authorization появляются в выводе логов, и задокументируйте политику сроков жизни токенов.

03

Доступ партнёров к API

Проблема

Партнёры хотят создавать заказы через ваш API со своих серверов. Некоторые интегрируются от имени многих мерчантов; другим нужен только доступ на чтение к отчётам.

Как решить

Предложите client credentials grant или API-ключи со scopes, если партнёрам нужна простота. Каждый партнёр получает отдельные test- и live-учётные данные со scopes вроде orders:create или reports:read и rate limits для каждого клиента. Привязывайте учётные данные к тенанту или мерчантам, от имени которых партнёр может действовать, и проверяйте это на сервере. Требуйте idempotency keys при создании заказов, чтобы ретраи никогда не дублировали заказы. Поддерживайте перекрывающиеся ключи для ротации без простоя, логируйте использование по клиентам и дайте партнёрам дашборд для отзыва и перевыпуска. Для важных партнёров используйте подписанные JWT client assertions вместо общих секретов.

Шпаргалка

Рекомендуемый flow для любого приложения
Authorization code + PKCE (code_challenge_method=S256)
Discovery-документ OIDC
GET https://auth.example.com/.well-known/openid-configuration
Сгенерировать PKCE challenge (bash)
printf %s "$VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '='
Установить Passport (Laravel)
php artisan install:api --passport
Создать PKCE (публичный) клиент в Passport
php artisan passport:client --public
Короткие access token в Passport
Passport::tokensExpireIn(CarbonInterval::minutes(30));
Требовать scope на роуте
->middleware(['auth:api', CheckToken::using('orders:read')])
Отозвать токен (RFC 7009)
POST /revoke token=...&token_type_hint=refresh_token
Claims для проверки в ID token
signature, iss, aud, exp, iat, nonce — затем связывать по sub
Machine-to-machine
grant_type=client_credentials&scope=reports:read

В моих реальных проектах

  • Я делаю Laravel API (Laravel с 8 по 13) с Passport для аутентификации по токенам, обслуживая веб-, мобильные и админ-клиенты из одного бэкенда.
  • Я реализовал вход через Google и Apple для веба и нативных мобильных приложений на платформе заказов для ресторанов, а также вход через Google, LinkedIn и Facebook с помощью Socialite на инвестиционном портале.
  • В multi-tenant POS SaaS аутентификация построена на JWT с подписью RS256, которые перепроверяются на каждом запросе с denylist, на 2FA для персонала и операторов и на device-токенах для касс.
  • Та же POS SaaS выдаёт партнёрам API-ключи со scopes, с разделением live/test, rate limits и ротацией, а также ограниченную по времени имперсонацию, требующую указать причину.

Вопросы и ответы

Q1Чем OAuth 2.0 отличается от OpenID Connect?

OAuth 2.0 — это фреймворк авторизации: он позволяет клиенту получить access token, чтобы вызывать API от имени пользователя в рамках одобренных scopes. Стандартно он ничего не говорит о том, кто пользователь. OpenID Connect — слой идентификации поверх: запрос scope «openid» заставляет сервер авторизации вернуть ещё и ID token — подписанный JWT с идентификатором пользователя, issuer, audience и сроком действия, — а также эндпоинт userinfo и discovery-метаданные. Итого: OAuth отвечает на вопрос «что этому приложению можно делать?», OIDC — «кто только что вошёл?». «Войти через Google» — это OIDC.

Q2Что такое PKCE и почему он теперь обязателен?

PKCE, Proof Key for Code Exchange, защищает authorization code flow от перехвата кода. Клиент генерирует случайный секрет (code_verifier), отправляет в запросе авторизации только его SHA-256-хэш (code_challenge), а позже при обмене кода на токены должен предъявить исходный verifier. Атакующий, который украл код — через вредоносное приложение с той же redirect-схемой, логи или утечку через referrer, — не сможет его обменять. Изначально PKCE придумали для мобильных и SPA-клиентов без секретов, но актуальные рекомендации (RFC 9700 и черновик OAuth 2.1) советуют его для всех клиентов, включая серверные веб-приложения.

Q3Где single-page приложению хранить токены?

Самый безопасный вариант — вообще не хранить их в браузере: backend-for-frontend (BFF) выполняет OAuth-флоу на сервере, держит токены у себя и выдаёт браузеру сессионную cookie с флагами HttpOnly, Secure, SameSite. JavaScript не может её прочитать, поэтому XSS-баг не украдёт долгоживущие токены. Если SPA всё же должно держать токены, храните access token только в памяти, делайте его короткоживущим и используйте ротацию refresh token со строгими сроками жизни. Избегайте localStorage для всего долгоживущего: любой внедрённый скрипт может его прочитать.

Q4JWT access token или opaque-токены?

JWT самодостаточны и подписаны, поэтому API может проверять их локально без сетевого вызова — быстро и удобно для распределённых сервисов. Минус — отзыв: JWT действителен до истечения срока, если не добавить denylist или проверку версии сессии. Opaque-токены — это случайные строки, которые API проверяет через introspection или запрос в базу, поэтому отзыв мгновенный, но ценой поиска на каждый запрос (его можно кэшировать). Распространённый компромисс: короткоживущие JWT access token на 5–15 минут, плюс отзываемые refresh token, плюс denylist для срочных случаев вроде скомпрометированного аккаунта.

Q5Как проектировать scopes?

Scopes описывают, что токену разрешено делать, поэтому называйте их по бизнес-возможностям: orders:read, orders:create, profile:write — а не по таблицам базы или по каждому эндпоинту. Держите набор небольшим и понятным на экране согласия. Запрашивайте только те scopes, которые нужны клиенту, и проверяйте их на каждом роуте. Помните, что scopes грубые: токен с orders:read всё равно должен проходить проверки на уровне объектов, чтобы пользователь A не мог прочитать заказ пользователя B. Для собственных приложений scopes тоже помогают ограничить ущерб — например, токену киоска или кассы можно дать гораздо меньше прав, чем админскому.

Q6Как правильно реализовать выход с OAuth?

У выхода несколько уровней. Локально — завершите сессию приложения и удалите сохранённые токены. На сервере авторизации — отзовите refresh token (эндпоинт отзыва по RFC 7009), чтобы он не мог выпускать новые access token. Уже выданные JWT access token остаются действительными до истечения срока, поэтому делайте их короткоживущими или добавляйте в denylist, если важно мгновенное отключение. Для SSO на OpenID Connect используйте RP-initiated logout, чтобы завершить и сессию у identity provider, и рассмотрите back-channel logout, чтобы уведомлять другие приложения. При смене пароля или подозрении на компрометацию отзывайте все токены пользователя.

Вопросы уровня senior

S1Вы бы делали собственный сервер авторизации или взяли управляемый identity provider?

Для большинства продуктов я бы не писал OAuth-сервер с нуля; я бы выбирал между сервером, интегрированным во фреймворк, вроде Laravel Passport, self-hosted продуктом вроде Keycloak и управляемым провайдером вроде Auth0, Okta, Entra ID или Cognito. Серверы на базе фреймворка дешёвы и держат пользователей в моей собственной базе — отлично для собственных веб- и мобильных клиентов и простого доступа партнёров. Управляемые провайдеры выигрывают, когда продукту нужны корпоративный SSO для каждого клиента, политики MFA, SCIM, сертификаты compliance и обнаружение аномалий; вы платите за пользователя и принимаете vendor lock-in и их модель данных. Self-hosted Keycloak — посередине: полный набор функций без платы за пользователя, но обновления, масштабирование и патчи безопасности на вас. Решающие факторы — требования клиентов (B2B SSO — один из главных), возможности команды и то, насколько идентификация центральна для продукта.

S2Как вы организуете авторизацию в системе из множества микросервисов?

Я отделяю аутентификацию от решений об авторизации. Edge-шлюз или каждый сервис проверяет access token локально — подпись, issuer, audience, срок действия — по закэшированному JWKS. Токены несут идентичность, тенанта и грубые scopes, а не все права, потому что большие наборы прав раздувают токены и устаревают. Тонкие решения принимаются в сервисе, владеющем данными, или в policy engine (OPA, Cedar или система на отношениях), которому подаются актуальные данные. Для вызовов service-to-service сервисы используют собственные client credentials или token exchange, чтобы получить токен с нужной audience, а не пробрасывают токен пользователя повсюду. Компромиссы: самодостаточные JWT быстрые, но медленно отзываются, поэтому сроки жизни остаются короткими; централизованные policy engines добавляют сетевой хоп, но держат правила согласованными и проверяемыми.

S3Что меняется в OAuth 2.1 и актуальных рекомендациях по сравнению с классическим OAuth 2.0?

Направление — закрепление выстраданных уроков; OAuth 2.1 пока черновик, но RFC 9700 уже фиксирует большую часть. PKCE становится обязательным в authorization code flow для всех клиентов. Implicit grant и resource owner password grant удаляются. Redirect URI должны совпадать точно. Bearer-токены нельзя передавать в query string URL. Refresh token публичных клиентов должны быть sender-constrained или ротироваться. Сверх ядра рекомендации добавляют sender-constrained токены (DPoP или mTLS) для ценных API, PAR для защиты параметров авторизации, короткие сроки жизни access token и идентификацию издателя для защиты от mix-up атак. На практике миграция обычно означает включить PKCE везде, убрать вход через password grant в мобильных приложениях в пользу code flow через браузер и ужесточить сроки жизни и ротацию.

Глоссарий

Authorization server
Сервис, который аутентифицирует пользователей и выдаёт access, refresh и ID token.
Access token
Короткоживущие учётные данные, которые клиент отправляет в API, чтобы доказать право на действие.
Refresh token
Долгоживущие учётные данные, используемые только для получения новых access token без повторного входа.
ID token
JWT из OpenID Connect, сообщающий клиенту, кто пользователь и как он вошёл.
Scope
Именованное разрешение, которое запрашивает клиент и выдаёт пользователь, например orders:read.
PKCE
Секрет для каждого входа и его хэш, которые не дают воспользоваться украденным authorization code.
JWKS
Опубликованный JSON-набор публичных ключей для проверки подписей токенов.
Client credentials grant
OAuth-флоу, в котором сервис аутентифицируется сам за себя, без участия пользователя.
DPoP
Механизм привязки токенов к ключу клиента, чтобы украденный токен нельзя было воспроизвести.

Есть идея продукта?Давайте создадим его.

Расскажите о своей идее, продукте или команде. Обычно отвечаю в течение одного рабочего дня.