Метод POST /api/v1/auth/refresh

Документация API: Фоновое обновление сессии (Refresh Token) с бесшовным перевыпуском Multi-Tenancy клеймов

Published

July 2, 2026

1 Функциональное назначение

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

В рамках микросервисной архитектуры этот эндпоинт решает критические инфраструктурные задачи:

  1. Обслуживание Dio Interceptor: Метод вызывается сетевым слоем Flutter-приложения строго в фоновом режиме, когда шлюз возвращает ошибку HTTP 401 Unauthorized на любой транзакционный запрос.
  2. Бесшовный перевыпуск контекста: Проверяет долгоживущий Refresh Token по базе сессий и генерирует новый короткоживущий Access Token (JWT), сохраняя актуальные JWT Private Claims home_group_id, account_type и user_id. Это гарантирует, что фоновые процессы сокетов и транзакции не прервутся для пользователя.
  3. Механизм обновления токенов (Refresh Token Rotation): Для защиты от компрометации, при каждом вызове старый Refresh-токен аннулируется, а клиенту выдается новая пара токенов, что предотвращает атаки типа Replay Attack.

2 Протокол взаимодействия (HTTP Контракт)

  • Метод: POST
  • Маршрут: /api/v1/auth/refresh
  • Формат данных: application/json

2.1 Спецификация заголовков (HTTP Headers)

Заголовок Обязательный Описание Пример значения
Content-Type Да Указывает на передачу строго типизированного JSON-пакета application/json
X-Request-ID Да Сквозной ID запроса для трассировки фоновой ротации сессии req-auth-ref-22aa

2.2 Спецификация тела запроса (Request Body)

В соответствии с требованиями безопасности, Refresh-токен передается в теле JSON-запроса (или извлекается из защищенных HttpOnly Cookies в зависимости от конфигурации окружения).

Поле Тип Обязательный Описание Пример значения
refresh_token String Да Долгоживущий токен обновления, выданный при входе в систему ref-token-xyz789...

2.2.1 Пример сырого JSON-запроса (Payload):

{
  "refresh_token": "ref-token-xyz789_v1_signature..."
}

2.3 Спецификация успешного ответа (Response Body)

2.3.1 HTTP 200 OK

Возвращается при успешной валидации сессии. Выдается обновленный JWT-токен.

{
  "status": "success",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3UrODgyMiIsImhvbWVfZ3JvdXBfaWQiOiJncm91cC03NzItYWxwaGEiLCJhY2NvdW50X3R5cCI6IkhPTUUiLCJleHAiOjE3ODMzMTMyMDB9.new_signature...",
  "expires_in": 3600
}

3 Схема обработки запроса пользователя (Mermaid)

На диаграмме представлена логика работы Dio Interceptor при фоновом обновлении токенов. Сервис Auth проверяет refresh_token по базе активных сессий, выполняет обновление, аннулирует старый ключ и выпускает новый JWT с сохранением JWT Private Claims.

sequenceDiagram
    autonumber
    participant APP as Flutter (Dio Interceptor)
    participant AS as Сервис Авторизации (FastAPI)
    participant DB as Реляционная СУБД (PostgreSQL)

    Note over APP, AS: Ранее: любой транзакционный метод вернул HTTP 401
    APP->>AS: HTTP POST /api/v1/auth/refresh (JSON с текущим refresh_token)
    activate AS
    
    AS->>DB: SELECT user_id, home_group_id, account_type, is_revoked FROM user_sessions WHERE token = refresh_token
    activate DB
    DB-->>AS: Возвращает метаданные сессии и статус токена
    deactivate DB
    
    note over AS: Шаг 4: Валидация срока жизни токена и флага is_revoked
    
    alt Токен валиден (Успешная ротация)
        AS->>DB: UPDATE user_sessions SET is_revoked = true WHERE token = old_token (Аннулирование)
        AS->>DB: INSERT INTO user_sessions (user_id, token, expires_at) VALUES (Новая сессия)
        note over AS: Шаг 7: Генерация нового Access Token (JWT) с перевыпуском Claims
        AS-->>APP: HTTP 200 OK (status: "success", token: "eyJ...", expires_in: 3600)
    else Токен просрочен ИЛИ уже был аннулирован (Reuse Detection)
        note over AS: Шаг 9: Каскадный сброс всех сессий юзера при подозрении на взлом
        AS->>DB: UPDATE user_sessions SET is_revoked = true WHERE user_id = target_user_id
        AS-->>APP: HTTP 401 Unauthorized (ERR-REFRESH-TOKEN-EXPIRED)
    end
    deactivate AS
    note over APP: Flutter обновляет токен в Secure Storage и бесшовно повторяет упавший запрос

4 Расшифровка шагов и логика ротации токенов

  • Шаг 1 (APP -> AS): Сетевой слой мобильного приложения (Dio Interceptor), перехватив ошибку HTTP 401 от транзакционного шлюза склада, замораживает очередь пользовательских запросов интерфейса. Из Flutter Secure Storage извлекается текущий refresh_token, и отправляется запрос HTTP POST /api/v1/auth/refresh.
  • Шаг 2 (AS -> DB): Микросервис Авторизации (FastAPI) принимает пакет и выполняет прямой запрос к таблице сессий PostgreSQL для проверки легитимности токена: SELECT user_id, home_group_id, account_type, is_revoked, expires_at FROM user_sessions WHERE token = $1.
  • Шаг 3 (DB -> AS): База данных возвращает метаданные сессии, включая признаки компрометации (is_revoked) и скоуп Multi-Tenancy изоляции, к которому привязан данный токен.
  • Шаг 4 (AS -> AS): Алгоритм бэкенда проводит комплексную валидацию: сверяет текущую дату с expires_at и проверяет, не равен ли флаг is_revoked значению true. Если проверки пройдены, запускается контур Token Rotation.
  • Шаг 5 (AS -> DB): В целях безопасности старый использованный refresh_token немедленно аннулируется в базе данных: UPDATE user_sessions SET is_revoked = true WHERE token = $1. Это защищает систему от повторного использования токена злоумышленником при перехвате трафика.
  • Шаг 6 (AS -> DB): В рамках той же атомарной транзакции генерируется и записывается новая строка для следующего долгоживущего токена обновления, продлевая сессию устройства в СУБД.
  • Шаг 7 (AS -> AS): Бэкенд заново собирает Payload для короткоживущего Access Token. Внутрь структуры JWT повторно упаковываются неизменяемые JWT Private Claims: home_group_id, account_type и user_id. Подпись токена обновляется криптографическим ключом.
  • Шаг 8 (AS -> APP): Сервер возвращает статус HTTP 200 OK и передает новый JWT. Сетевой слой Flutter обновляет локальный кэш токенов, разблокирует очередь и бесшовно для пользователя повторяет тот транзакционный запрос (например, списание порции пирога), который изначально упал по ошибке 401. Пользователь не видит экранов авторизации и не замечает смены сессии.
  • Шаг 9 (AS -> DB): Вилка обнаружения атаки (Reuse Detection): Если система видит, что клиент прислал refresh_token, у которого is_revoked == true, это сигнализирует о том, что токен уже был украден и использован ранее. Включается жесткая fail-safe блокировка: бэкенд аннулирует абсолютно все активные сессии данного user_id в базе данных, мгновенно выкидывая все смартфоны пользователя на экран логина, блокируя действия потенциального хакера.

5 Спецификация ответов сервера (Response Body) и ошибок

5.1 1. Спецификация структуры сессий PostgreSQL (Token Rotation Layer)

Для поддержки безопасного механизма обновления токенов и определения повторного использования угнанных ключей (Reuse Detection), база данных хранит метаданные сессий в изолированной таблице.

-- Таблица сессий и долгоживущих Refresh-токенов
CREATE TABLE user_sessions (
    id BIGSERIAL PRIMARY KEY,
    user_id VARCHAR(50) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token VARCHAR(512) NOT NULL UNIQUE,       -- Хэш или тело Refresh-токена
    is_revoked BOOLEAN NOT NULL DEFAULT FALSE,-- Флаг аннулирования токена (использован/украден)
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Индекс для мгновенной верификации токена при фоновом обновлении Dio Interceptor
CREATE INDEX idx_user_sessions_lookup ON user_sessions(token) WHERE is_revoked = FALSE;

5.2 2. Вилки исключений и обработка ошибок

5.2.1 Ошибка: Токен просрочен или аннулирован (HTTP 401 Unauthorized — Шаг 9)

Выбрасывается на Шаге 9, если refresh_token истек по времени жизни, либо если сработал триггер детекции повторного использования (is_revoked == true). Это жесткий сигнал для Flutter полностью сбросить локальный кэш и принудительно выкинуть пользователя на экран авторизации.

  • Заголовки ответа (Response Headers):
    • Content-Type: application/json
  • Тело ответа (Response Body):
{
  "error_code": "ERR-REFRESH-TOKEN-EXPIRED",
  "message": "Сессия авторизации полностью истекла или была аннулирована из соображений безопасности. Необходим повторный вход.",
  "details": {
    "action": "Clear Flutter Secure Storage, disconnect WebSockets, and route user to Login Screen."
  }
}

5.2.2 Ошибка: Токен поврежден или имеет неверный формат (HTTP 422 Unprocessable Entity)

Выбрасывается бэкенд-шлюзом FastAPI, если переданная строка в поле refresh_token пустая или нарушает валидацию длины Pydantic-модели.

{
  "error_code": "ERR-VALIDATION-FAILED",
  "message": "Передан некорректный или пустой токен обновления refresh_token.",
  "details": [
    {
      "loc": ["body", "refresh_token"],
      "msg": "Field constraints violation: token string cannot be empty",
      "type": "value_error.str.min_length"
    }
  ]
}