Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → SSO, OAuth 2.0 и OpenID Connect: как устроен вход через провайдера

SSO на OAuth 2.0 и OpenID Connect: как это работает изнутри

Код-флоу с PKCE, токены, scope, провайдеры и типичные дыры реализации: как разработчику правильно встроить вход через Google, GitHub или Keycloak.

Прямой ответ

Три термина, которые путают: OAuth 2.0 — протокол авторизации («приложение получает право действовать от имени пользователя» — доступ к API, не вход), OpenID Connect (OIDC) — слой аутентификации поверх OAuth («кто этот пользователь» — id_token с профилем — именно это нужно для кнопки «Войти через Google»), SSO — пользовательский опыт (один вход — много сервисов — технически достигается общим IdP через OIDC/SAML); правильный паттерн входа для веб/SPA/мобилки в 2026 — Authorization Code Flow с PKCE (RFC 7636 — защита от перехвата кода — обязателен даже для конфиденциальных клиентов по OAuth 2.1-практике; Implicit Flow — мёртв/запрещён, Resource Owner Password Credentials — запрещён — «форма логина, отправляющая пароль Google вашему бэкенду» — антипаттерн и красный флаг ревью); поток: (1) клиент генерирует случайные state (защита от CSRF — CSRF-практика) и code_verifier (PKCE — хранит в сессии/памяти), (2) редирект на authorization_endpoint провайдера (client_id, redirect_uri — зарегистрированный точно, scope=openid profile email, code_challenge=SHA256(verifier)), (3) пользователь логинится у провайдера (ваш код пароля не видит — главный выигрыш SSO), (4) редирект обратно с code (одноразовый, живёт минуты), (5) бэкенд меняет code+code_verifier на токены на token_endpoint (по TLS, с client_secret для конфиденциальных клиентов), (6) получает access_token (для API, короткий) + id_token (JWT с claims: sub/iss/aud/exp — JWT-механика) + refresh_token, (7) валидирует id_token (подпись по JWKS провайдера, iss, aud, exp, nonce — пропуск валидации — дыра №1 в самодельных реализациях — «просто декодировали JWT» — подделывается за секунды) и создаёт свою сессию (своя cookie/JWT — не «пробрасываем id_token как сессию» — он для вас, не для ваших клиентов); scope — принцип минимума (openid profile email — вход; openid + чтение календаря — разные scope — «приложение просит все права» — пользовательское недоверие и риск); провайдеры: managed (Google/GitHub/Apple — ноль инфраструктуры) против self-hosted Keycloak (корпоративный IdP — SAML-легаси, кастомные флоу, MFA, аудит — секреты для client_secret — vault); «купить или собрать»: auth0/workos/keycloak против самописа — самописный OAuth-сервер — почти всегда ошибка (OWASP-позиция — крипто/флоу-баги дорого стоят — используйте готовые IdP+библиотеки).

Токены, сессии, logout: что делать после входа

Access/refresh-механика: access_token (5–60 мин жизни — компромисс утечки короток) — предъявляется к API провайдера (Bearer-заголовок); refresh_token (дни/недели — вращение (rotation) при каждом использовании — reuse detection — украденный refresh виден как «старый использован повторно» — провайдер рвёт всю цепочку); id_token — одноразовый артефакт входа (не хранить/не предъявлять — прочитали claims, создали сессию — выбросили); ваша сессия: server-side (Redis — структуры — отзыв мгновенный) или JWT-схема (httpOnly-cookie — XSS-стойкость — XSS-защита); mapping-пользователей: ключ — sub провайдера (стабильный уникальный — НЕ email — «email сменился — пользователь потерял аккаунт» — классическая дыра; таблица identities (user_id, provider, sub) — мульти-провайдерность из коробки — «войти тем же Google вторым способом привязки»); logout-реальность: локальный (убить свою сессию — обязательно) + RP-Initiated Logout OIDC (редирект на end_session_endpoint провайдера — «вышел везде» — поддерживает не всё — обещания в UI соразмерять); single logout (SLO) в SAML-мире — боль (фронт-канал редиректы — ненадёжно) — back-channel logout OIDC (провайдер шлёт уведомление вашим сервисам — надёжнее — Keycloak умеет); BFF-паттерн для SPA (backend-for-frontend: токены живут на бэке, браузер — только httpOnly session-cookie — токены не светятся в JS-контексте — XSS-кража невозможна — рекомендовано OAuth 2.1/OWASP для браузерных приложений — CORS/браузерная модель — «SPA хранит access_token в localStorage» — антипаттерн 2018-го, живущий до сих пор).

Практический сценарий: вход через Google + GitHub в SaaS за неделю

Контекст: B2B-SaaS (Python/FastAPI + React-SPA), требование: «вход через Google Workspace + GitHub, свои пароли позже». Решение архитектуры: BFF + code flow + PKCE (не «SPA сам ходит в Google» — токены на бэке — валидация — решение в ADR); реализация по шагам: (1) регистрация OAuth-приложений (Google Cloud Console: redirect_uri точно — — отдельный клиент для [локальной разработки](./django-lokalnaya-razrabotka-nastroyka.md); GitHub OAuth App — аналогично); client_secret — в Vault/env ([секреты](./sekrety-i-env-peremennye-v-razrabotke.md), не в код — скан секретов в CI — [github-actions](./github-actions-python-matrica.md)); (2) библиотека вместо самописа (authlib/httpx — флоу/PKCE/JWKS-валидация — не «requests и на глаз»); (3) state+nonce+PKCE — из коробки библиотеки (самопис забыл бы nonce — replay id_token); (4) валидация id_token: iss (accounts.google.com), aud (ваш client_id), exp, подпись по JWKS (кэш JWKS с ротацией по kid — «подпись не проверяли» — самая частая дыра самописа); (5) identities-таблица (user_id, provider, sub — unique по (provider, sub) — email как атрибут, не ключ); (6) сессия — server-side в Redis (TTL 8ч, refresh-скольжение; отзыв — мгновенный del — [кэш-паттерны](./redis-structures-kesh-ocheredi-paterny.md)); (7) **найденные на ревью дыры** (до прода — польза чеклиста): redirect_uri проверялся по префиксу (app.example.com→app.example.com.evil.com` — провайдер сверяет точно, но наш фолбэк-редирект — нет — open redirect — OWASP A10); email_verified не проверялся (Google-аккаунт с неподтверждённым email — слияние аккаунтов по email — захват — фикс: слияние только при email_verified=true); post-login redirect не по whitelist (фишинг-вектор — фикс: относительные пути или доменный whitelist); (8) тесты: мок JWKS/токенов (поддельный iss/aud/exp/подпись — негативные кейсы — «валидация есть» без негативных тестов — фикция); (9) метрики: логины по провайдерам, ошибки валидации (всплеск — атака/баг — алерт); итог: неделя до прода, паролей нет (позже — Keycloak с password+MFA — SSO-фундамент тот же); вывод команды: «библиотека + чеклист валидации + негативные тесты» — самописный флоу стоил бы тех же недель плюс дыры из пункта 7.

FAQ

OAuth 2.0 против OpenID Connect — что использовать для входа? Для «Войти через X» — всегда OIDC (OAuth — про доступ к API; «используем OAuth для логина» — обычно означает самодельный профиль поверх OAuth без id_token — дыры: нет стандартных claims/валидации — «подделка входа через access_token к /userinfo» — исторические дыры именно этого класса); OIDC даёт id_token (подписанный, валидируемый), discovery (/.well-known/openid-configuration — эндпоинты без хардкода), стандартный logout; практический маркер: провайдер публикует openid-configuration — берите OIDC-флоу; только API-доступ нужен (без входа) — чистый OAuth (client credentials для сервис-сервис, code flow для «от имени пользователя»); смешение («логиним по OAuth-токену GitHub») — работает, но валидация рукотворная — механика JWT — лучше OIDC там, где он есть.

Почему Implicit Flow запрещён и что вместо него в SPA? Implicit (токен сразу во фрагменте URL) — утечки через history/referer/прокси, отсутствие sender-constraining, токены в браузере — OAuth 2.1 вычеркнул его; замена для SPA — два варианта: code flow + PKCE в SPA (токены в памяти JS — XSS-риск жив — приемлемо при сильной CSP) или BFF (токены на бэке, браузер — только сессионная httpOnly-cookie — рекомендуемый дефолт — XSS красть нечего — XSS/CSP); PKCE при этом обязателен в обоих (защищает code от перехвата — «код украли в редиректе — верификатора нет — обмен провалился»); «у нас SPA на localStorage с токенами» — миграция на BFF — стандартный рефакторинг (структура — тонкий auth-слой на бэке).

Keycloak или managed-провайдер (Auth0/WorkOS)? Managed: ноль инфраструктуры, быстрый старт, цена растёт с MAU (тысячи юзеров — заметно), данные пользователей у вендора (GDPR-вопрос — комплаенс-контекст); Keycloak: бесплатно, полный контроль, SAML-легаси/кастомные флоу/MFA-политики, но — это сервис, который надо эксплуатировать (деплой, БД, обновления — CVE выходят — зависимости, TLS); критерии: стартап/публичный SaaS — managed (или просто Google/GitHub-кнопки — ноль стоимости); корпоративный контекст/свой IdP/данные должны остаться у вас — Keycloak; «Keycloak на прод без опыта эксплуатации» — недооценка (обновления с breaking-миграциями БД — стенд + миграции-дисциплина — своя БД Keycloak — бэкапы — PITR).

Как тестировать SSO локально и в CI? Локально: реальный провайдер с localhost-redirect (Google/GitHub позволяют — самый честный вариант) или локальный Keycloak в docker-compose (realm-импорт из JSON — воспроизводимо — «поднял IdP одной командой»); CI: без живых провайдеров (нестабильно/квоты) — мок token/JWKS-эндпоинтов (подписанные тестовым ключом id_token'ы — негативные: истёкший/чужой aud/кривая подпись — моки); контрактные тесты против discovery-документа провайдера (эндпоинты сменились — тест красный до прода — контрактная практика); E2E с Keycloak в testcontainers (паттерн — полный флоу в CI — медленнее, но ловит интеграционные регрессии — ночной пайплайн); золотое правило: флоу-логика (валидация/сессии) тестируется моками быстро, интеграция с реальным IdP — выборочно/ночью.

Источники: RFC 6749 (OAuth 2.0), OpenID Connect Core 1.0, RFC 7636 (PKCE) — ссылки в шапке.