OAuth 2.0 + OpenID Connect: Authorization Code + PKCE flow, M2M Client Credentials, refresh token rotation with theft detection, ADR comparing OAuth/OIDC vs SAML vs custom session-based auth.
Любой современный продукт упирается в один и тот же вопрос: как пустить пользователя внутрь — и как пустить другие приложения действовать от его имени без раздачи паролей. Раньше это решалось «дайте нам ваш логин и пароль от Gmail, мы посмотрим контакты» — антипаттерн, который убил бы любой комплаенс в 2026.
OAuth 2.0 решает делегирование: третья сторона получает scoped токен (только то, что согласовал пользователь), может быть отозвана, не знает пароль. OIDC надстраивается сверху и добавляет identity-слой — «кто этот пользователь» через стандартизированный ID token (JWT с claims вроде sub, email, name).
90% кнопок «Sign in with Google / Apple / GitHub» — это OIDC. 90% «Connect Slack / Calendly / Notion» — это OAuth с user-consent на конкретные scopes. Если вы строите B2C или SaaS — без OIDC сегодня не выйти на рынок (пользователи привыкли к one-click signup, форма с email+password = friction).
Понимать OAuth/OIDC критично ещё и потому, что большинство security-инцидентов 2022-2024 касались именно их: GitHub OAuth-токены утекли через Heroku/Travis-CI (2022), Microsoft Storm-0558 подделал tokens через украденный signing key (2023), Okta — украденный session token у support engineer (2022). Это значит — каждый разработчик, трогающий auth, должен знать threat model.
OAuth — про что приложению можно делать от моего имени (action). OIDC — про кто я (identity). Это два разных вопроса, которые часто решаются одним стандартом.
Четыре роли, которые надо держать в голове:
И три типа токенов:
Главная ментальная ловушка: путать ID token и access token. ID token — это «карточка с фото» (кто я, для frontend), access token — это «ключ от двери» (что я могу делать, для backend). Отправлять ID token в API как Bearer-token — антипаттерн (audience неверный, нет scope).
Канвас разбит на пять групп — каждая представляет одну роль в OAuth-экосистеме:
/authorize для user consent, /token для exchange, /jwks с публичными ключами, user DB с consent recordsEdges — это физические соединения, а не направление данных. Браузер связан и с IdP, и с API, и с backend RP — потому что во flow он реально общается со всеми тремя через redirect-чейн. Worker связан с /token (для exchange) и с API (для вызовов).
Четыре сценария анимации, переключаются пиллами внизу:
Это единственный flow, который стоит использовать в новых web/mobile приложениях. OAuth 2.1 draft делает PKCE обязательным для всех клиентов — публичных и confidential.
Шаги (упрощённо):
code_verifier (random 43-128 chars) и code_challenge = SHA256(verifier)state (random) — защита от CSRF/authorize?code_challenge=...&state=.../callback?code=...&state=...state совпадаетcode + code_verifier на токены через back-channelSHA256(code_verifier) == code_challenge — выдаёт access + refresh + id_tokenPKCE защищает от code interception attack — даже если злоумышленник перехватит authorization_code (например, через malicious app на mobile с тем же registered scheme), без code_verifier он бесполезен.
Backend сервис вызывает API без user-context: cron jobs, microservice-to-microservice, аналитические пайплайны. Worker отправляет client_id + client_secret на /token, получает access_token (без refresh — каждый раз новый exchange).
Ключевая дисциплина: не реюзайте user access_token для service calls. Это impersonation, нарушает audit log, делает невозможной трассировку «кто реально дёрнул эту ручку».
Long-lived refresh tokens — большая дыра, если их не ротировать. Pattern: на каждом использовании refresh_token IdP инвалидирует старый и выдаёт новый. Если старый refresh_token используется повторно (например, утёк через XSS) — IdP знает: это либо race condition, либо theft, и инвалидирует всю token family (весь refresh chain пользователя). User вынужден заново залогиниться — no silent compromise.
Сценарий 4 — это интерактивный разбор trade-offs (см. ниже).
Context. Стартуем новый продукт. Нужна authentication + (опционально) authorization для third-party. Что выбрать?
| Option | + | - | Когда брать |
|---|---|---|---|
| Custom session (cookie + server session) | Простота, полный контроль, минимум deps | Своя реализация = своя дыра. MFA, password reset, brute-force protection — всё писать. Нет SSO. | Internal admin tools, low-scale внутренние продукты |
| SAML 2.0 | Enterprise IdP standard (Okta, Azure AD). Compliance сразу. | XML + signatures, сложно дебажить, XML injection vulnerabilities. Нет нативной поддержки SPA/mobile. | Enterprise B2B, где customer требует SAML SSO с конкретным IdP |
| OAuth 2.0 + OIDC | Modern standard. JWT для stateless API. PKCE для public clients. Sign in with Google/GitHub. Granular scopes. | Сложность: PKCE, JWT verify, JWKS rotation, token storage pitfalls. | Default для всего нового в 2026 |
Decision. Для нового продукта в 2026:
Consequences. Принимаем сложность OAuth/OIDC ради federation, granular scopes и compliance. Минимизируем её через managed IdP — не пишем свой /token endpoint, не реализуем PKCE state machine руками.
/.well-known/openid-configuration доступен публично, можно curl и посмотреть все endpoints.apps/api/src/betterAuth.ts).iss без validation — атакующий пишет свой iss в форжнутом JWT. Hardcode allowed issuers, не парсите из token.aud check — token, выданный для service A, принимается service B. Каждый Resource Server должен проверять aud matches его identifier.redirect_uri — auth code phishing. Обязательный exact-match allowlist (не regex, не wildcard)./.well-known/jwks.json с кешем (TTL ~24h).OAuth/OIDC — это сложность. Не тащите её туда, где она не нужна:
~/.config/app/token файла достаточно. OAuth Device Code Flow только если интегрируетесь с уже существующим IdP (GitHub CLI делает так — но потому что нужны GitHub OAuth scopes).Главная эвристика: если managed IdP не нужен и federation не нужен — OAuth не нужен. Custom session проще, понятнее и более тестабилен.
Связанные концепты в этом курсе: ::concept{slug="authn-models"} (модели аутентификации), ::concept{slug="jwt-best-practices"} (как правильно валидировать JWT), ::concept{slug="rate-limiting"} (защита /token endpoint).