Перейти к основному содержимому

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

API использует JWT (JSON Web Token) через библиотеку djangorestframework-simplejwt 5.4.0.


Как устроен JWT-токен

JWT — это строка из трёх частей, разделённых точкой:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header
.eyJ1c2VyX2lkIjo0MiwiZXhwIjoxNzU0MDAwMDAwfQ ← Payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature

Header (алгоритм)

{
"alg": "HS256",
"typ": "JWT"
}

Алгоритм подписи — HMAC-SHA256. Сервер подписывает токен секретным ключом (SECRET_KEY в Django). Изменить содержимое токена без знания ключа невозможно.

Payload (данные)

{
"token_type": "access",
"user_id": 42,
"jti": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"exp": 1754000000,
"iat": 1753999700
}
ПолеОписание
token_typeaccess или refresh
user_idID пользователя в БД
jtiУникальный ID токена (JWT ID) — используется для отзыва
expUnix timestamp — когда токен истекает
iatUnix timestamp — когда токен был выдан

Signature (подпись)

HMAC-SHA256(
base64url(header) + "." + base64url(payload),
SECRET_KEY
)

Сервер при каждом запросе пересчитывает подпись и сверяет с той, что в токене. Если не совпадает — 401 Unauthorized.


Пара access + refresh

Система выдаёт два токена одновременно:

ТокенНазначениеВремя жизни
accessАвторизация запросов12 часов
refreshПолучение нового access-токена7 дней
Blacklist активен

В базе данных существуют таблицы OutstandingToken и BlacklistedToken (приложение rest_framework_simplejwt.token_blacklist). Все выданные refresh-токены регистрируются, при использовании или отзыве — попадают в blacklist. Это означает, что использованный refresh-токен нельзя использовать повторно даже если срок жизни не истёк.

Логика:

  • access передаётся в заголовке каждого API-запроса
  • refresh хранится на клиенте и используется только для обновления access
  • refresh не передаётся в обычных запросах — только на /api/auth/token/refresh/
Ротация refresh-токенов включена

ROTATE_REFRESH_TOKENS = True — при каждом вызове /auth/token/refresh/ сервер возвращает и новый access, и новый refresh. Старый refresh-токен немедленно аннулируется.

Это значит: клиент обязан каждый раз сохранять новый refresh-токен из ответа. Если использовать старый повторно — получишь 401. Если потерял refresh-токен — нужна повторная аутентификация.

Клиент Сервер
│ │
│── POST /auth/token/ ─────────>│
│<─ { access, refresh } ────────│
│ │
│── GET /api/clients/ │
│ Authorization: Bearer │
│ <access> ─────────────────>│
│<─ 200 OK ─────────────────────│
│ │
│ ... время жизни истекло ... │
│ │
│── GET /api/clients/ ─────────>│
│<─ 401 Token expired ──────────│
│ │
│── POST /auth/token/refresh/ │
│ { refresh } ──────────────>│
│<─ { access (новый) } ─────────│
│ │
│── GET /api/clients/ ─────────>│
│ Authorization: Bearer │
│ <новый access> ────────────>│
│<─ 200 OK ─────────────────────│

Триггеры истечения и ошибок

Когда access-токен становится недействительным

ПричинаHTTP-статусcode в ответе
Истёк срок жизни (exp в прошлом)401token_not_valid
Подпись не совпадает (токен изменён)401token_not_valid
Передан refresh вместо access401token_not_valid
Заголовок Authorization отсутствует401not_authenticated
Неверный формат заголовка401not_authenticated

Когда refresh-токен становится недействительным

ПричинаHTTP-статусДействие
Истёк срок жизни401Требуется повторная аутентификация
Уже был использован (если включена ротация)401Требуется повторная аутентификация
Токен отозван (через jti blacklist)401Требуется повторная аутентификация

Рекомендуемая логика на клиенте

1. Выполнить запрос с access-токеном
2. Получили 401?
├── Попробовать обновить через refresh-токен
│ ├── Успех → сохранить новый access, повторить запрос
│ └── Ошибка → перенаправить на страницу входа
└── Другая ошибка → обработать по коду

Получение токена через логин/пароль

POST https://api.salonai.ru/api/auth/token/
Content-Type: application/json

{
"username": "your_username",
"password": "your_password"
}

Ответ:

{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Получение токена через Telegram OTP

Используется для входа по номеру телефона. Двухшаговый процесс:

Клиент Сервер Telegram
│ │ │
│── POST /auth/otp/request/│ │
│ { phone } ────────────>│ │
│ │── Отправить код ──>│
│ │ │── Код в бот ──> Пользователь
│<─ { status: "sent" } ────│ │
│ │ │
│ Пользователь читает код в Telegram │
│ │ │
│── POST /auth/otp/verify/ │ │
│ { phone, code } ──────>│ │
│ │── Проверить код │
│<─ { access, refresh } ───│ │

Шаг 1 — Запросить OTP:

POST https://api.salonai.ru/api/auth/otp/request/
Content-Type: application/json

{
"phone": "+79991234567"
}
{
"status": "sent",
"message": "Код отправлен в Telegram-бот @analisys1_bot"
}

Шаг 2 — Подтвердить код:

POST https://api.salonai.ru/api/auth/otp/verify/
Content-Type: application/json

{
"phone": "+79991234567",
"code": "123456"
}
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Обновление access-токена

POST https://api.salonai.ru/api/auth/token/refresh/
Content-Type: application/json

{
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Ответ — возвращаются оба токена (ротация включена):

{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Сохраняй новый refresh

Старый refresh-токен после этого запроса недействителен. Обязательно перезаписывай его в хранилище на стороне клиента.


Использование токена

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Пример:

curl -X GET "https://api.salonai.ru/api/clients/" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"

Управление профилем

Профиль текущего пользователя

GET /api/auth/me/
Authorization: Bearer <token>
{
"id": 42,
"phone": "+79991234567",
"first_name": "Иван",
"last_name": "Иванов",
"email": "ivan@example.com",
"role": "client"
}

Обновление профиля

PATCH /api/auth/me/
Authorization: Bearer <token>

{
"first_name": "Иван",
"email": "new@example.com"
}