Два слова, которые путают
- Аутентификация: кто ты? Проверка личности (пароль, код, ключ). Ошибка:
401 Unauthorized. - Авторизация: что тебе можно? Проверка прав. Ошибка:
403 Forbidden.
HTTP не хранит состояние, поэтому после входа клиент должен с каждым запросом приносить доказательство, что он уже вошёл. Есть два основных подхода: сессия и токен.
Сессии на куках
- Пользователь вводит логин и пароль.
- Сервер создаёт запись сессии (в памяти, Redis или БД) и отдаёт её id в куке.
- Браузер сам прикладывает куку к каждому запросу на этот сайт.
Set-Cookie: sid=8f3a...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000
Атрибуты куки:
HttpOnly: JS не видит куку (document.cookie), XSS её не украдёт.Secure: только по HTTPS.SameSite:Strictне шлёт куку ни с каких запросов с чужих сайтов,Lax(в Chrome так ведёт себя кука без атрибута) шлёт только при переходе по ссылке методом GET,Noneшлёт всегда и требуетSecure.
Плюс сессий: выход и блокировка мгновенные, просто удалить запись. Минус: нужно общее хранилище сессий, если серверов несколько.
JWT
JWT (JSON Web Token) это три части в base64url через точку: header.payload.signature.
// payload
{ "sub": "42", "role": "admin", "iat": 1760000000, "exp": 1760000900 }
Payload не зашифрован, а только подписан: любой может его прочитать, но не может изменить, не сломав подпись. Сервер проверяет подпись секретом (HS256) или публичным ключом (RS256, ES256) и не ходит в базу: токен самодостаточен.
Главный минус: токен нельзя отозвать до истечения exp, если не завести чёрный список (а это снова хранилище). Поэтому используют пару:
- access token: короткий (5-15 минут), шлётся в
Authorization: Bearer <token>; - refresh token: длинный (дни, недели), лежит в
HttpOnlyкуке, нужен только для получения нового access. Хранится на сервере, чтобы его можно было отозвать. Хорошая практика: ротация, при каждом обмене выдаётся новый refresh, а старый становится недействительным. Повторное использование старого означает кражу, и тогда гасят всю цепочку.
Где хранить токен на фронте
localStorage: просто, но любой XSS-скрипт прочитает токен.HttpOnlyкука: недоступна JS, но шлётся автоматически, значит нужна защита от CSRF (SameSite, CSRF-токен).- Память (переменная): безопаснее всего, но теряется при перезагрузке, поэтому её сочетают с refresh в
HttpOnlyкуке.
Частая схема: access в памяти, refresh в HttpOnly; Secure; SameSite куке на путь /auth/refresh.
OAuth 2.0 и OpenID Connect
OAuth 2.0 это протокол делегирования доступа: пользователь разрешает приложению действовать от его имени в другом сервисе, не отдавая пароль. OIDC надстройка над OAuth, которая добавляет аутентификацию: id_token (JWT с данными пользователя). «Войти через Яндекс» это OIDC.
Поток Authorization Code + PKCE (стандарт для SPA и мобильных):
- Клиент генерирует
code_verifierи его хешcode_challenge, редиректит на провайдера сcode_challengeиstate. - Пользователь входит у провайдера, тот редиректит обратно с
code. - Клиент меняет
code+code_verifierна токены.
state защищает от CSRF на возврате, PKCE не даёт украденному code превратиться в токены. Implicit flow (токен сразу в URL) устарел и в OAuth 2.1 убран.
Пароли
Пароли хранят только как медленный хеш с солью: argon2id (рекомендация OWASP), bcrypt, scrypt. Не SHA-256 и не MD5: они быстрые, их перебирают миллиардами в секунду.
Частые ошибки
- Класть в payload JWT секреты, считая, что он зашифрован.
- Принимать JWT с
alg: noneили не фиксировать алгоритм на сервере. - Делать access-токен на месяц «чтобы не мучиться с refresh».
- Отвечать 403 вместо 401 на отсутствие токена (и наоборот).
Что спросят на собесе
- Чем аутентификация отличается от авторизации?
- Сессии или JWT: плюсы, минусы, как отозвать JWT?
- Зачем нужен refresh-токен и где хранить токены на фронте?
- Что такое HttpOnly, Secure, SameSite?
- Как работает OAuth 2.0 Authorization Code с PKCE?