Паттерны аутентификации

Fresh-подход в аутентификации строится вокруг идеи короткоживущих токенов доступа и обновления сессии без повторного ввода учетных данных. В JavaScript-приложениях этот паттерн применяется для снижения рисков компрометации, упрощения масштабирования и отказа от серверных сессий.

Ключевая особенность — токен, используемый для доступа к защищённым ресурсам, всегда «свежий», то есть имеет минимальное время жизни и регулярно обновляется.


Базовая модель: access token + refresh token

Fresh-аутентификация почти всегда реализуется через пару токенов:

  • Access token

    • короткий срок жизни (секунды или минуты);
    • передаётся с каждым запросом;
    • используется исключительно для авторизации.
  • Refresh token

    • длительный срок жизни (дни или недели);
    • используется только для получения нового access token;
    • не передаётся к бизнес-эндпоинтам.

В JavaScript-экосистеме чаще всего access token — это JWT, а refresh token — либо JWT, либо случайная строка, сохранённая на сервере.


Причины появления Fresh-паттерна

Минимизация ущерба при утечке Если access token украден, он перестаёт быть валидным через короткое время.

Отказ от серверных сессий Сервер не хранит состояние пользователя между запросами.

Горизонтальное масштабирование Любой экземпляр backend-сервиса может валидировать токен без shared-storage.

Чёткое разделение ответственности Access token — для API, refresh token — только для обновления сессии.


Архитектура потока аутентификации

Первичная аутентификация

  1. Клиент отправляет логин и пароль.

  2. Сервер проверяет учетные данные.

  3. Сервер выдаёт:

    • access token (короткий TTL);
    • refresh token (длинный TTL).
  4. Refresh token сохраняется:

    • в httpOnly cookie или
    • в защищённом хранилище (mobile / desktop).

Access token сохраняется в памяти приложения.


Авторизованный запрос

  1. Клиент отправляет HTTP-запрос.

  2. В заголовке Authorization передаётся:

    Bearer <access_token>
  3. Сервер:

    • проверяет подпись;
    • проверяет срок действия;
    • извлекает user id и claims.

Сервер не обращается к базе данных для проверки сессии.


Истечение access token

Когда access token становится невалидным:

  1. Сервер возвращает 401 Unauthorized.

  2. Клиент автоматически инициирует запрос обновления.

  3. Refresh token отправляется на отдельный endpoint.

  4. Сервер:

    • валидирует refresh token;
    • выдаёт новый access token;
    • при необходимости — новый refresh token.

Клиент повторяет исходный запрос.


Refresh token rotation

Один из ключевых элементов Fresh-паттерна — ротация refresh token.

Принцип:

  • каждый refresh token используется один раз;
  • при обновлении сервер выдаёт новый refresh token;
  • предыдущий немедленно инвалидируется.

Это позволяет обнаруживать повторное использование токена — признак компрометации.


Реализация refresh-эндпоинта в JavaScript

Типичный endpoint обновления:

  • принимает refresh token из cookie;
  • проверяет подпись или наличие в базе;
  • проверяет, не был ли токен отозван;
  • генерирует новую пару токенов.

Важно:

  • endpoint не принимает access token;
  • endpoint не возвращает пользовательские данные;
  • endpoint не используется в браузерных перехватчиках.

Хранение токенов на клиенте

Access token

Рекомендуется:

  • хранить в памяти приложения;
  • не сохранять в localStorage;
  • пересоздавать при перезагрузке страницы через refresh token.

Причина — защита от XSS.


Refresh token

На вебе:

  • httpOnly;
  • secure;
  • sameSite=strict или lax.

На мобильных платформах:

  • Keychain / Keystore;
  • зашифрованное хранилище.

Refresh token никогда не должен быть доступен JavaScript-коду браузера.


Обработка параллельных запросов

Типичная проблема Fresh-паттерна — несколько запросов одновременно получают 401.

Решение:

  • единый refresh-lock;
  • пока выполняется обновление, остальные запросы ожидают результат;
  • после обновления повторяются с новым access token.

Без этого возникает лавина refresh-запросов.


Инвалидация сессий

Fresh-аутентификация не означает отсутствие контроля.

Поддерживаются сценарии:

  • выход из системы;
  • смена пароля;
  • блокировка аккаунта.

Реализация:

  • хранение refresh token id в базе;
  • blacklist или whitelist;
  • удаление всех активных refresh-токенов пользователя.

Access token при этом просто доживает свой TTL.


Claims и минимализм

Access token должен содержать:

  • идентификатор пользователя;
  • минимальный набор ролей или прав;
  • идентификатор сессии (опционально).

Запрещено:

  • email;
  • персональные данные;
  • любые чувствительные поля.

JWT — это не хранилище состояния, а транспорт утверждений.


Fresh-паттерн и CSRF

Если refresh token хранится в cookie:

  • refresh-эндпоинт подвержен CSRF;

  • используется:

    • sameSite;
    • CSRF-токен;
    • double submit cookie.

Access token в заголовке CSRF-уязвимым не является.


Отличия от классических сессий

Характеристика Fresh Cookie-сессии
Хранение состояния Клиент Сервер
Масштабирование Простое Требует shared-storage
TTL Короткий Длинный
Защита при утечке Высокая Низкая
Контроль Через refresh Через сервер

Типичные ошибки реализации

  • длинный TTL access token;
  • хранение access token в localStorage;
  • отсутствие ротации refresh token;
  • использование refresh token для API-запросов;
  • логирование токенов;
  • отсутствие защиты refresh-эндпоинта.

Каждая из этих ошибок полностью нивелирует преимущества Fresh-паттерна.


Fresh-аутентификация в SPA и SSR

SPA

  • access token живёт в runtime;
  • refresh token в cookie;
  • обновление через interceptor.

SSR

  • refresh происходит на сервере;
  • access token прокидывается в запросы;
  • cookie остаётся единственным хранилищем.

Fresh-подход одинаково применим в обоих сценариях.


Fresh-паттерн как стандарт де-факто

В современных JavaScript-приложениях Fresh-аутентификация используется:

  • в микросервисах;
  • в serverless-архитектурах;
  • в публичных API;
  • в мобильных клиентах.

Это не библиотека и не протокол, а архитектурный паттерн, объединяющий безопасность, производительность и масштабируемость.