Веб-разработка
Аутентификация: JWT, OAuth, Sessions
В 2014 году Slack обнаружил, что атакующий получил токены пользователей через XSS в чате. Токены лежали в localStorage. Через два года Slack полностью перешёл на HttpOnly cookies + короткие JWT - и сэкономил на инциденте, который мог стоить как Equifax (1.4 млрд долларов).
- **Slack (2014)**: XSS в чате + localStorage = массовая утечка токенов
- **Equifax (2017)**: 1.4 млрд долларов штрафов за компрометацию учётных данных 147 млн пользователей
- **OAuth 2.0 Security BCP (RFC 8252)**: Implicit Flow официально deprecated, требуется PKCE
- **Auth0/Okta**: индустриальный стандарт - гибрид access JWT (15 мин) + refresh token в БД
- **Chrome 80+ (2020)**: SameSite=Lax default - тихая глобальная мера против CSRF
JWT: stateless токены
JSON Web Token - это компактный self-contained формат для передачи claims между сторонами. Структура: header.payload.signature, три base64url-сегмента через точки. Сервер подписывает токен своим секретом (HS256) или приватным ключом (RS256), клиент шлёт его обратно при каждом запросе.
**Главная боль JWT:** отозвать невозможно. Если access token утёк, он работает до истечения exp. Решения: короткие TTL (5-15 мин), refresh token rotation, blocklist в Redis для досрочного отзыва.
Что обеспечивает signature в JWT?
OAuth 2.0 и OIDC
OAuth 2.0 - протокол делегирования доступа: приложение получает токен для работы с ресурсами от имени пользователя, не зная его пароля. OpenID Connect (OIDC) - надстройка над OAuth, добавляющая identity layer: сервер выдаёт id_token (JWT) с claims о пользователе.
**Authorization Code Flow + PKCE** - современный стандарт для веба и мобильных: 1. Клиент редиректит на authorize endpoint с code_challenge 2. Пользователь логинится у провайдера 3. Провайдер возвращает code на redirect_uri 4. Клиент меняет code + code_verifier на access_token + id_token на token endpoint
**Implicit flow устарел.** Запрещён в OAuth 2.1 (черновик) и в спецификации OAuth Security BCP. Использовать Authorization Code + PKCE даже для SPA.
Зачем нужен PKCE в authorization code flow?
Server sessions vs JWT
Server session - классический подход: сервер хранит session state в Redis или БД, клиент носит opaque session_id в cookie. JWT - stateless подход: state в самом токене, сервер только проверяет подпись.
**Trade-off матрица:** | Параметр | Server Session | JWT | |---|---|---| | Отзыв | мгновенный | требует blocklist | | Масштабирование | нужен shared store | stateless | | Размер на каждый запрос | 32 байта | 500-1500 байт | | Хранилище БД | требуется | не нужно | | Изменение прав | сразу | до истечения TTL |
**Гибридный подход (используют Auth0, Okta):** короткий access JWT (15 мин) + длинный refresh token в БД. Access stateless для скорости, refresh stateful для отзыва. Best of both worlds.
Команда выбирает между session-based и JWT для нового приложения. Когда JWT лучше?
Cookies: httpOnly, Secure, SameSite
Хранение токена в localStorage - известная уязвимость: любой XSS читает токен и отправляет атакующему. Стандартное решение - класть токен в cookie с тремя обязательными флагами: HttpOnly, Secure, SameSite.
**localStorage = XSS-уязвимость.** Любой `<script>` на странице (через рекламу, npm-зависимость с supply chain attack, отзывы пользователей без sanitize) может прочитать токен. HttpOnly cookie этому недоступны.
**CSRF protection в дополнение к SameSite:** double-submit cookie (CSRF-token в cookie + header, сервер сверяет) или synchronizer pattern (one-time token в форме). SameSite=lax сам по себе - первая линия защиты, но не единственная.
JWT в localStorage безопасен, потому что подписан криптографически.
Подпись защищает от подделки токена, но не от его кражи. Если XSS читает токен и отправляет атакующему - тот использует подписанный валидный токен и проходит проверку.
Yahoo, Twitter, Slack теряли пользовательские сессии через XSS + localStorage именно так. Цифровая подпись подтверждает, что данные не изменены, но не привязывает токен к конкретному пользователю или браузеру. Правильное решение - HttpOnly cookie плюс короткий TTL плюс строгий CSP.
Почему `localStorage.setItem('token', jwt)` - плохая практика?
Ключевые идеи
- JWT - stateless токены с подписью. Self-contained: claims читаются без БД. Главная проблема: невозможно отозвать без blocklist
- OAuth 2.0 = делегирование, OIDC = identity layer. Современный стандарт - Authorization Code Flow + PKCE. Implicit flow устарел
- Server sessions stateful (отзыв мгновенный, нужен shared store). JWT stateless (масштабируется, но не отозвать). Гибрид: access JWT + refresh в БД
- Cookies лучше localStorage. Обязательные флаги: HttpOnly (защита от XSS), Secure (только HTTPS), SameSite=lax (защита от CSRF)
Связанные темы
Аутентификация - сквозной слой, который влияет на API-дизайн, транспорт и хранение секретов; пересекается с предыдущими темами курса:
- GraphQL — auth слой над API
- REST API Design — Authorization header + Bearer токены
- Node.js и Express — passport.js, express-session, jsonwebtoken
- HTTP в браузере — cookies, CORS, secure flags на транспортном уровне
Вопросы для размышления
- Когда выберете JWT, а когда server sessions? Назовите по два критерия для каждого
- Зачем PKCE нужен SPA, если client_secret там и так нельзя хранить?
- Опишите атаку, которая работает с JWT в localStorage и не работает с HttpOnly cookie
- Что делать, если access token утёк, но refresh token в БД?
Связанные уроки
- web-13 — GraphQL требует особой настройки аутентификации
- web-15 — После auth - продвинутые паттерны безопасности
- sec-01 — Основы security перед JWT и OAuth
- crypto-19-hash-functions — Хеш-функции за HMAC подписями JWT
- net-15-tcp-basics — HTTPS/TLS - обязательная основа для OAuth flow