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

Главная → Статьи → JWT и OAuth: что нужно знать разработчику

JWT и OAuth 2.0: токены, потоки и безопасность без мифов

Разбор для практика: чем аутентификация отличается от авторизации, как устроен JWT и где его ломают, OAuth 2.0 authorization code + PKCE, хранение токенов, refresh и типовые уязвимости.

Прямой ответ

Две вещи, которые путают чаще всего: OAuth 2.0 — протокол делегирования доступа («приложение получает ограниченный доступ к моим данным у провайдера» — вход через Google тоже построен на нём), а JWT — формат токена (подписанный JSON с утверждениями; сам по себе не «система входа»). Рабочая сборка 2026: вход пользователя — сессия или OAuth code flow с PKCE (для SPA/мобильных — обязателен), доступ к API — короткоживущий access-токен (JWT или opaque) + refresh-ротация, проверка на сервере — подпись/aud/exp, а не «просто декодировал». Хранение на клиенте: httpOnly-cookie (веб) или безопасное хранилище ОС (мобильные); localStorage — признанный риск (XSS читает всё).

JWT анатомия: header.payload.signature

JWT — три base64url-части: заголовок (алгоритм), payload (claims: sub — кто, exp/iat/nbf — время, aud/iss — кому/от кого, роли/scope), подпись (HS256 — общий секрет, RS256/ES256 — приватный ключ подписывает, публичный проверяет). Критично понимать: payload не зашифрован — это читаемый JSON (никаких паролей/персональных данных внутри), защита — от изменения, не от чтения. Проверка на сервере обязательна полная: подпись, exp, iss/aud (токен вашего сервиса, а не соседнего), алгоритм из белого списка (атака «alg: none» и подмена RS→HS — классика из учебников). «Декодировал и поверил» — самая распространённая JWT-уязвимость в самописных реализациях; берите проверенные библиотеки (PyJWT/authlib) и не изобретайте валидацию.

Жизненный цикл: короткие access, ротация refresh

Access-токен живёт минуты (5–15) — компрометация ограничена окном; продление — refresh-токеном (долгоживущий, хранится строже, ротация: каждый обмен выдаёт новый refresh, старый гасится — украденный токен всплывает при первом же легитимном использовании). Отзыв доступа — слабое место stateless-JWT (подписан — значит валиден до exp): решается коротким TTL, deny-list по jti для критичных отзывов (разлогин везде, компрометация) или переходом на opaque-токены с проверкой в хранилище (stateful, но отзыв мгновенный). Логаут «на всех устройствах» и смена пароля должны инвалидировать сессии/refresh — иначе «выйти» не означает выйти.

OAuth 2.0: потоки, которые остались в 2026

Живые и рекомендуемые: Authorization Code + PKCE — универсальный (веб, SPA, мобильные, CLI): пользователь авторизуется у провайдера, приложение обменивает код на токены; PKCE (динамический secret в запросе) закрывает перехват кода — без него flow для публичных клиентов считается сломанным. Client Credentials — сервис-сервис без пользователя (ваш бэкенд → чужой API). Умершие/запрещённые: Implicit (токены во фрагменте URL — утёк в историю/логи) и Resource Owner Password Credentials (приложение видит пароль пользователя — против смысла делегирования) — в новом коде не используются. Роли/permissions в OAuth — через scope (на что выдан доступ), а не «всё или ничего».

Веб-практика: где хранить и как передавать

Передача access-токена — Authorization: Bearer <token> (заголовок, не query-параметр — URL оседают в логах прокси). Хранение в браузере: httpOnly+Secure+SameSite cookie — XSS не достаёт токен (цена: CSRF — закрывается SameSite/токеном формы); localStorage — доступен любому скрипту на странице (любая XSS = угон сессии) — для access-токенов не рекомендуется; компромисс «SPA без своего бэкенда» — токены только в памяти + silent refresh. Серверная сторона: секреты клиентов (client_secret) и ключи подписи — в переменных окружения/secret-хранилище (правила); приватный ключ подписи JWT не покидает сервер авторизации. Смежная гигиена — redirect_uri только из белого списка (иначе код утекает на чужой домен) и state-параметр против CSRF в flow.

Когда своя аутентификация уместна

«Вход по паролю» (email+пароль, своя сессия) — нормально и проще для продукта без «входа через Google»: хеширование паролей — argon2/bcrypt (никаких MD5/sha1), сессии — серверные (cookie → запись в БД/Redis — отзыв мгновенный, stateless-JWT не обязателен). JWT оправдан, когда: несколько сервисов проверяют токен без общего хранилища (микросервисы — границы и паттерны в статье про микросервисы), федерация/SSO между системами, токены для внешних API с самопроверкой. Ошибочный аргумент «JWT, потому что быстрее» — сессия в Redis проверяется за миллисекунду; безопасность и управляемость важнее микросекунд. Статусы API при провале — 401 против 403 (семантика — в статье про дизайн REST).

Практический сценарий: вход через Google за час

Соберём типовую интеграцию «Войти через Google» для веб-приложения, чтобы увидеть поток целиком. Регистрация в Google Cloud Console: OAuth-клиент типа Web application, разрешённые redirect_uri — точно те, что в приложении (опечатка в URI — самая частая ошибка «redirect_uri_mismatch»). На стороне приложения: эндпоинт /auth/google генерирует случайный state (кладёт в сессию — защита от CSRF), собирает URL авторизации с client_id, scope openid email profile, response_type=code, code_challenge (PKCE) и редиректит пользователя. Google показывает согласие, возвращает код на /auth/callback вместе с state — приложение сверяет state, обменивает код на токены по HTTPS (client_secret из переменной окружения), валидирует ID-токен: подпись по JWKS Google, iss, aud — свой client_id, exp. Из проверенного токена берутся sub (стабильный идентификатор пользователя — именно он ключ в вашей таблице пользователей, не email, который может смениться) и email для отображения. Пользователь создаётся или находится, и приложение выдаёт уже свою сессию или свой access-токен — дальше внутренний мир живёт без Google. Токены Google при этом хранить не нужно (если не требуется доступ к данным пользователя — тогда refresh-токен в зашифрованном виде). Весь поток — две ручки и сотня строк кода на проверенной библиотеке; самописная проверка JWT — единственное место, где легко совершить настоящую уязвимость.

Практический сценарий: проверка access-токена в FastAPI

Разберём серверную часть целиком — десять решений, из которых состоит «правильная» проверка. Зависимость объявляет схему HTTPBearer — заголовок Authorization виден в OpenAPI. Токен декодируется библиотекой (PyJWT) с алгоритмом из белого списка (RS256) — поле alg из заголовка токена не является доверенным источником выбора. Ключ проверки — из JWKS эмитента, с кешированием и обновлением при ротации (библиотека делает сама). Проверяются: подпись, exp (плюс небольшой clock skew в секунды), nbf, iss — ровно ваш эмитент, aud — ровно ваш сервис (токен соседнего сервиса с тем же эмитентом не подходит). Провал любого пункта — 401 с одинаковой нейтральной деталью (не «подпись невалидна» против «истёк» — лишняя информация атакующему не нужна; детали — в логи). Из валидных claims берётся sub — стабильный идентификатор, по которому поднимается пользователь/сессия из хранилища (привилегии — из актуального состояния, не из клеймов токена). Отзыв — короткий TTL плюс deny-list по jti для критичных случаев (разлогин по требованию). Всё это — тридцать строк зависимости, переиспользуемой всеми защищёнными роутами; и каждая из трёх десятков строк закрывает конкретный класс атак, которые в самописных проверках пропускаются регулярно.

FAQ

JWT или сессии для обычного веб-приложения? Сессии (cookie + серверное хранилище) проще в безопасности и отзыве; JWT — когда состояние не хранить (горизонтальный масштаб без общего стора) или токены читают несколько сервисов. Для монолита с Redis сессии — честный дефолт.

Можно ли хранить в JWT роли и права? Можно, с оговоркой: данные устаревают до exp (пользователя разжаловали — токен ещё жив). Короткий TTL делает окно приемлемым; для чувствительных операций — проверка прав на сервере по актуальному состоянию, не по клейму токена.

Что такое PKCE простыми словами? Клиент генерирует случайный секрет на каждый flow, посылает его хэш в запросе авторизации, а оригинал — при обмене кода на токены: перехваченный код без секрета бесполезен. Для SPA/CLI/mobile — обязательная часть code flow.

Как проверить JWT провайдера (Google/Keycloak)? Публичные ключи провайдера (JWKS-эндпоинт) + проверка iss/aud/exp стандартной библиотекой; свои реализации проверки подписи — источник уязвимостей. Ключи кешируются и ротируются провайдером — библиотека делает это за вас.

Источники: RFC 7519 (JWT), RFC 6749 (OAuth 2.0), oauth.net — ссылки в шапке.