RU

Аутентификация

Аутентификация

Базовые понятия

  • Аутентификация отвечает на вопрос «кто это?» и подтверждает личность пользователя (пароль, токен, WebAuthn).
  • Авторизация отвечает на вопрос «что разрешено?» — проверяет права уже известного пользователя.
  • Учёт (accounting/auditing) фиксирует действия пользователя: когда зашёл, что сделал, какие данные изменил.

Схема входа

  1. Клиент отправляет доказательства личности (логин/пароль, OTP, подпись и т. п.).
  2. Сервер проверяет их и выдаёт «доказательство входа» (сессия, access token).
  3. Клиент прикладывает это доказательство к каждому запросу, пока оно не протухло/не отозвано.

Ниже — основные способы хранить такое доказательство.

Сессионная аутентификация

  1. /login → сервер создаёт запись в хранилище (Redis, БД) с ID сессии и данными пользователя.
  2. ← браузер получает cookie с sessionId.
  3. Клиент отправляет куку на каждый запрос → сервер находит сессию и восстанавливает пользователя.

Плюсы:

  • легко отзываться (удалить запись в хранилище) — пригодится при logout со всех устройств.
  • можно хранить дополнительные данные (роль, timeouts, CSRF-токены).

Минусы:

  • требуется отдельное хранилище (например, таблица в БД или Redis), за которым нужно отдельно следить.
  • браузер сам прикладывает куки → уязвимость к CSRF без доп. мер.

Обязательные флаги cookie: HttpOnly, SameSite, Secure. Подробности — в конспекте Сеть.

JWT (JSON Web Token)

JWT — компактный, URL-safe токен c тремя частями header.payload.signature, подписанный секретом/SSH-ключом. Подписанный payload содержит поля с информацией о сессии: sub, exp, role, aud и др.

Как работает

  1. /api/login → сервер проверяет пользователя и подписывает JWT c коротким TTL (15–60 мин).
  2. Клиент сохраняет токен (обычно в localStorage или HttpOnly cookie).
  3. Все запросы → отправлять токен в заголовке Authorization: Bearer <jwt>.
  4. Сервер проверяет подпись и срок действия; при успехе восстанавливает контекст пользователя.

Плюсы:

  • Stateless — не нужно централизованное хранилище (сервер не хранит сессию).
  • Можно работать с несколькими сервисами: любой сервис с секретом проверит токен. (Напр. если есть несколько бэкендов и в случае JWT не нужно иметь коллекцию сессий в БД или отдельный бэкенд централизованный для авторизации)

Минусы:

  • Отозвать токен заранее сложно → приходится хранить black list токенов или использовать короткий TTL.
  • Размер токена растёт по мере добавления полей в payload → влияет на размер заголовков.
  • Хранение в localStorage уязвимо к XSS — см. раздел XSS. Поэтому нужно хранить в cookie.

Refresh tokens и ротация

  • Access token делаем короткоживущим, а refresh token — долгоживущим и храним только в HttpOnly cookie.
  • При 401 клиент обращается к /refresh → сервер проверяет refresh token, выдаёт новую пару и, по желанию, ревоукит старый.
  • Важно хранить refresh tokens серверно (whitelist) и делать ротацию (меняем и access, и refresh при каждом обновлении).

OAuth 2.0 и OpenID Connect

Используем, когда нужен вход через сторонний провайдер (Google, GitHub) или доступ к API от имени пользователя.

  • OAuth 2.0 — протокол делегирования доступа.
  • Authorization Code Flow (с PKCE для SPA) — безопасный способ получить access token через редирект и одноразовый код.
  • Scopes описывают, что можно делать: read:user, repo, openid email.
  • OpenID Connect — надстройка над OAuth 2.0, которая даёт id_token (JWT) с данными пользователя. Используем для SSO (Single Sign-on - одна авторизация для разных сайтов, вроде YouTube и Google).

Важно валидировать redirect_uri, state и nonce, хранить client secret на сервере и регулярно вращать ключи.

Авторизация и контроль доступа

RBAC (role-based)

Наборы прав привязываем к ролям (admin, editor, viewer). В БД в коллекции users есть поле role. Наиболее простая реализация авторизации, но и наименее гибкая.

ABAC (attribute-based)

Когда RBAC не хватает и приходится добавлять доп. условия к ролям, напр. if (user.role === 'admin' && user.id === comment.authorId) Тогда удобно их вынести в централизованную функцию-хелпер.

// policy.js
export function can(user, action, resource) {
  switch (action) {
    case "addComment":
      return user.role === 'admin';
    case "editComment":
      return user.role === "admin" && user.id === comment.authorId;
    default:
      return false;
  }
}

PBAC / Policy-based

Правила описываем декларативно: напр. в JSON и используем движок (библиотеку напр. Casbin) для интерпретации этих правил на фронте и бэке.

// policies/post.json

{
  "version": 1,
  "policies": [
    { "effect": "allow", "role": "editor", "action": "posts.edit", "resource": "post" },
    { "effect": "allow", "role": "author", "action": "posts.edit", "resource": "post", "condition": "resource.authorId == user.id" },
    { "effect": "deny", "role": "*", "action": "posts.delete", "resource": "post" }
  ]
}

Политики можно менять без релиза — достаточно обновить файл или конфигурацию.

Scope-based

Классический подход для OAuth и API. JWT-токен содержит перечень разрешений (scopes), определяющих, какие действия пользователь может выполнять. Бэкенд использует эти скоупы, чтобы решать, имеет ли клиент право вызывать конкретный эндпоинт. Фронтенд может декодировать токен и применять эти же разрешения для управления интерфейсом (например, скрывать недоступные кнопки или страницы).

{
  "sub": "123",
  "exp": 1736342261,
  "scope": "users:read orders:write"
}

Практика

  • правила и роли храним в БД/конфигурации, кэшируем и инвалидируем при обновлении;
  • frontend отдаём минимальный список разрешений (['posts.edit']), UI скрывает недоступные действия;
  • backend всегда делает финальную проверку (нельзя доверять только фронту);
  • в микросервисах выносим общую библиотеку или отдельный сервис авторизации, чтобы все сервисы проверяли права одинаково.

Хранение секретов и валидация

  • Пароли хешируем (bcrypt/argon2/scrypt), используем соль и параметризацию (work factor).
  • При регистрации и смене пароля — rate limiting + капча/механизм задержки.
  • Верифицируем email/телефон, чтобы уменьшить количество одноразовых аккаунтов.
  • Логируйте попытки входа и храните аудит в неизменяемом хранилище.

Многофакторная аутентификация (MFA)

Второй фактор — что-то, что у пользователя есть (телефон, ключ), является (биометрия) или знает (PIN).

  • TOTP (Google Authenticator): сервер хранит секрет, выдаёт QR-код, проверки через code = totp(secret, timestamp).
  • FIDO2/WebAuthn: криптография с ключом, работает без паролей (passkeys). Часто реализуют через navigator.credentials.
  • Backup codes — одноразовая матрица для восстановления.

На фронте — обязательное UX: предупреждение о невозможности входа без второго фактора и маршруты для восстановления.

Типовые угрозы и защита

  • CSRF — защищаемся SameSite + CSRF-токены, см. подробности в конспекте Сеть.
  • XSS — токены в памяти или HttpOnly cookie, проверяем инпуты, см. раздел XSS.
  • MITM и перехват трафика — используем HTTPS/HSTS и защищённые каналы, подробнее в Сеть.
  • Brute-force (атака, когда спамят логин/пароль пытаясь подобрать) — rate limiting, блокировка IP, reCAPTCHA.
  • Credential stuffing — мониторим утечки, внедряем защиту на основе аномалий (невозможные путешествия, новые устройства).
  • Session fixation — регенерация идентификаторов сессии напр. после смены прав (роли).

Если приложение SPA:

  • избегаем хранения access token в localStorage → лучше HttpOnly cookie + SameSite=strict.
  • при необходимости работать с небезопасными запросами — используем fetch с credentials: 'include' и настройками CORS (см. CORS).