1. «Я вошёл в систему, а меня не узнают?». Как всё началось

OAuth2, JWT, сессионная аутентификация… Способов аутентификации множество, и в большинстве случаев их достаточно. Я тоже так думал.

  • Когда я добавлял OAuth2 в собственный почтовый клиент или в MyGPT от ChatGPT, было ощущение «вот это пользовательский опыт»;
  • Для одностраничных Django‑приложений лучшим вариантом оказалась сессионная аутентификация;
  • При разделённой архитектуре фронтенд/бэкенд самым чистым решением выглядел JWT.

Однако одна комбинация однажды полностью сломалась – асинхронные задачи (Celery). Пользователь нажимает кнопку, а вместо того, чтобы бекенд сразу обработал запрос, работа передаётся удалённому AI‑серверу или воркеру. И воркер спрашивает:

«Эй… запрос получен, но для кого? request.user у меня нет».

Робот‑воркер с API‑ключом передаёт письмо

2. Проблема «бэкенд ↔ бэкенд» + «асинхронный воркер (Celery)»

Ключевой момент, заставивший меня внедрить API‑ключ, был в том, что между бекенд‑серверами появляется Celery‑воркер.

  1. Пользователь отправляет HTTP‑запрос;
  2. Бэкенд кладёт задачу в очередь;
  3. Celery‑воркер берёт задачу и отправляет асинхронный запрос на какой‑то вычислительный сервер.

Самая боль в этой цепочке:

  • У воркера нет request.user;
  • Нет сессии (это не браузер);
  • JWT сложно использовать – непонятно, кто, где и как будет хранить и передавать токен;
  • OAuth2 требует пользовательского взаимодействия и просто не подходит.

Когда JWT и сессии теряют силу, остаётся один вопрос:

«Как воркеру указать, от имени какого клиента (тенанта/пользователя) выполнять задачу?»

3. Для воркера важнее «идентификация», чем «аутентификация»

В веб‑запросе «аутентификация = вход», а «вход = пользователь». Воркер же – это не человек, а приложение, которое самостоятельно «берёт» процессор. Поэтому сначала нужна идентификация, а аутентификацию можно решить простыми HMAC или secret‑key.

  • Задача должна выполняться с данными клиента A;
  • Результат должен сохраняться в ресурсах клиента A;
  • Тариф, права и квоты учитываются по клиенту A.

Пытаться впихнуть JWT или сессию в эту схему приводит к росту сложности: генерация, хранение, передача, истечение, обновление токенов. И возникает непреодолимое чувство: «Какой смысл выдавать JWT серверу, если пользователь не использует браузер прямо сейчас?» – идея была быстро отвергнута.

4. Решение: API‑ключ оказался простым и мощным

Мы внедрили API‑ключ и сразу решили проблему.

  • Воркер при внутреннем запросе передаёт один заголовок для аутентификации и идентификации;
  • Сервер по ключу мгновенно определяет, чей это запрос;
  • Обновление/замена ключа (ротация) становится предельно прозрачной.

Пример запроса к вычислительному серверу:

POST /v1/ai/jobs
Authorization: Api-Key <KEY>
Content-Type: application/json

{ "job_id": "...", "payload": {...} }

Воркеру не нужен request.user – достаточно отправить запрос с API‑ключом, а принимающий бэкенд определит пользователя по этому ключу.

5. Ключи, привязанные к пользователям, упростили эксплуатацию

Меня особенно порадовал тот факт, что обычные библиотеки вроде rest_framework_api_key предоставляют только сам ключ, но в моём случае важно было соединить «ключ ↔ пользователь (клиент)».

Мы унаследовали AbstractAPIKey и создали CustomAPIKey, связав её внешним ключом с моделью AUTH_USER:

from django.conf import settings
from rest_framework_api_key.models import AbstractAPIKey
from django.db import models

class CustomAPIKey(AbstractAPIKey):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="api_keys"
    )
    is_test = models.BooleanField(default=False)  # для тестовых/стейджинг‑ключей

Эта привязка открыла массу возможностей, выходящих за рамки простой аутентификации.

6. Автоматическая выдача ключей per‑user и её операционные плюсы

При регистрации пользователю автоматически создаётся ключ. Это дало несколько преимуществ.

1) Управление валидностью стало простым

  • Можно быстро просмотреть, деактивировать или удалить ключ конкретного пользователя;
  • При удалении пользователя связанные ключи исчезают автоматически (FK cascade).

2) Ротация ключей стала тривиальной

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

3) Тарифы/квоты/права теперь привязаны к пользователю

  • Ограничения задаются не по ключу, а по пользователю, что упрощает логику биллинга;
  • Не нужно каждый раз определять, чей запрос стоит за конкретным ключом.

4) Один пользователь может иметь несколько ключей

Флаг is_test показывает своё достоинство. Поскольку связь не OneToOne, а FK, один пользователь может иметь несколько ключей для разных целей:

  • Тестовый ключ для стейджинга;
  • Продакшн‑ключ для реального использования;
  • Разделение логов и мониторинга между тестовым и боевым трафиком.

7. Выбор аутентификационного механизма – не вопрос «лучшего», а вопрос «подходящего»

Итоги, которые я сейчас считаю оптимальными:

  • OAuth2 – для интеграций с внешними сервисами, когда важен пользовательский consent;
  • Сессионная аутентификация – для одиночных Django‑приложений, где важна скорость разработки и простота;
  • JWT – для разделённых фронтенд/бэкенд, мобильных и SPA‑клиентов;
  • API‑ключ – для бекенд‑бекенд, автоматизации, воркеров и батч‑заданий, где запросы инициируются не пользователем.

Как только в цепочку попадает Celery‑воркер, попытка «унифицировать всё через логин» только усложняет систему. Здесь API‑ключ стал идеальным спасением.

8. Заключение

Для людей (браузеров, приложений) естественно использовать сессии, JWT или OAuth2. Воркер же – процесс, которому нужно знать, чей это запрос. Мы перешли на API‑ключ не из-за громоздкой безопасности, а потому что это было самое простое решение в данном контексте. Привязав ключи к пользователям, мы превратили их из просто средства доступа в полноценный операционный рычаг.

А вы часто используете API‑ключи? Я считаю, что даже ограниченное их применение может принести большую пользу. Надеюсь, мой опыт вдохновит вас на более гибкие решения.


Связанные статьи