Аутентификация: токены и cookies
Аутентификация: токены и cookies
В уроке про cookies (6-3) мы разобрали, как HTTP хранит состояние через сессионные cookies. Теперь углубим тему: как современные веб-приложения аутентифицируют пользователей через сессии, JWT-токены и OAuth.
Два подхода: сессии и токены
В вебе есть два фундаментальных подхода к аутентификации:
Сессионная аутентификация (stateful): сервер хранит информацию о сессии (в памяти, базе данных, Redis). Клиенту выдаётся ID сессии в cookie:
Клиент → POST /login {login, password}
Сервер → проверяет, создаёт сессию { id: "abc123", userId: 42, expires: ... }
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax
Клиент → GET /api/me
Cookie: session_id=abc123
Сервер → ищет сессию по ID, находит userId=42
Токенная аутентификация (stateless): сервер никого не хранит. После логина выдаёт подписанный токен, содержащий данные пользователя. Клиент прикрепляет токен к каждому запросу в заголовке Authorization:
Клиент → POST /login {login, password}
Сервер → проверяет, создаёт JWT: { userId: 42, exp: 1714500000 }
подписывает секретным ключом
возвращает: { token: "eyJhbGciOi..." }
Клиент → GET /api/me
Authorization: Bearer eyJhbGciOi...
Сервер → проверяет подпись, извлекает userId=42
JWT: структура токена
JWT (JSON Web Token) — самый популярный формат токенов. Это три части, разделённые точкой, каждая в base64:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyLCJleHAiOjE3MTQ1MDAwMDB9.abc123signature
Часть 1 (Header): {"alg":"HS256","typ":"JWT"}
Часть 2 (Payload): {"userId":42,"exp":1714500000}
Часть 3 (Signature): HMAC-SHA256(Часть1 + "." + Часть2, secret)
Подпись гарантирует, что токен не подделан. Проверить подпись может любой, у кого есть секретный ключ. Изменить данные в payload без знания ключа невозможно — подпись не сойдётся.
Важно: JWT не шифрует данные (по умолчанию). Payload в base64 — это не шифрование, это кодирование. Любой может раскодировать payload и прочитать содержимое. Поэтому в JWT не кладут пароли, email'ы и другие чувствительные данные. Только ID и технические флаги.
Сессии или токены: что выбрать
| Характеристика | Сессии (cookies) | JWT-токены |
|---|---|---|
| Где хранить на клиенте | Cookie (автоматически) | localStorage / память JS |
| Масштабирование | Нужен общий сторадж для сессий (Redis) или sticky sessions | Без состояния — любой сервер обрабатывает |
| Отзыв | Удалить сессию на сервере — мгновенно | Токен живёт до истечения (нет встроенного отзыва без чёрного списка) |
| Безопасность XSS | HttpOnly cookie не видна JS | Токен в localStorage доступен JS (уязвим к XSS) |
| Безопасность CSRF | Уязвим (нужны SameSite + токен) | Не уязвим (токен не отправляется автоматически) |
| Мобильные приложения | Сложнее (cookie не нативные) | Естественно (заголовок Authorization) |
Общее правило: для веб-приложений с серверным рендерингом — сессии (cookie + HttpOnly). Для SPA + мобильные клиенты — JWT (заголовок Authorization). Многие системы комбинируют оба подхода.
OAuth 2.0 и сторонний вход
OAuth 2.0 — протокол делегирования доступа. Это когда сайт говорит «войти через Google»:
- Пользователь на
example.comнажимает «Войти через Google». - Браузер перенаправляется на
accounts.google.com/o/oauth2/auth. - Пользователь разрешает
example.comдоступ к профилю. - Google выдаёт временный код (authorization code).
- Сервер
example.comобменивает код на access-токен, обращаясь к Google API напрямую. example.comсоздаёт свою сессию/JWT для пользователя.
OAuth 2.0 не про аутентификацию сам по себе — это протокол авторизации (доступа). Но на практике его используют для аутентификации через социальные сети (Social Login). OpenID Connect (OIDC) — надстройка над OAuth 2.0, стандартизирующая именно аутентификацию.
Где хранить токен на клиенте
Это горячая тема в безопасности фронтенда:
| Способ | Плюс | Минус |
|---|---|---|
| localStorage | Простой, не отправляется автоматически | Уязвим к XSS (злоумышленник читает через JS) |
| HttpOnly cookie | Не видна JS (защита от XSS) | Уязвима к CSRF (отправляется автоматически) |
| Память JS (переменная) | Наиболее безопасна | Теряется при перезагрузке страницы |
Компромиссный подход для SPA: access-токен в памяти (или localStorage), refresh-токен в HttpOnly Secure SameSite cookie. Access-токен короткоживущий (5–15 минут), refresh-токен позволяет получить новый без повторного ввода пароля.
Проверь себя
- Чем JWT-токен принципиально отличается от сессионной cookie?
- Можно ли подделать JWT, если знаешь payload, но не знаешь секретный ключ?
- Почему хранение токена в localStorage опасно?
- Сессионная cookie содержит только ID сессии — сервер ищет данные по ID (stateful). JWT содержит данные внутри себя и подписан — сервер проверяет подпись, не обращаясь к хранилищу (stateless).
- Нет. Любое изменение payload изменит хэш, и подпись не сойдётся. Сервер обнаружит несовпадение и отвергнет токен.
- Любой JavaScript на странице имеет доступ к localStorage. При успешном XSS злоумышленник может прочитать токен и отправить на свой сервер — доступ к аккаунту потерян.
Что унести с урока
- Сессионные cookies (stateful) — сессия на сервере, ID в HttpOnly cookie. JWT (stateless) — данные в подписанном токене.
- JWT не шифрует по умолчанию — не класть чувствительные данные в payload.
- Компромисс: access-токен короткоживущий, refresh-токен в HttpOnly cookie.
- OAuth 2.0 + OIDC для социального входа — стандарт для делегирования аутентификации.
В последнем уроке модуля соберём ключевые security-заголовки — чеклист для сервера, который должен быть на каждом production-сайте.