1. "Je suis connecté, mais vous ne me reconnaissez pas" (début du problème)
OAuth2, JWT, authentification par session… il existe une multitude de méthodes, et dans la plupart des cas elles suffisent. J'ai moi‑même suivi cette logique.
- Quand j'ai ajouté OAuth2 à mon client mail maison ou à MyGPT de ChatGPT, j'ai pensé « voilà la vraie expérience utilisateur »
- Une application web monolithique sous Django fonctionne parfaitement avec l'authentification par session
- Dans les architectures front‑end / back‑end séparées, JWT est la solution la plus propre
Cependant, un jour, ces combinaisons ont toutes cédé en même temps.
Le coupable : les tâches asynchrones (Celery). Quand l'utilisateur clique sur un bouton, le back‑end ne traite pas directement la requête ; il la délègue à un serveur de calcul IA ou à un worker distant. Et le worker répond :
"Euh… j'ai reçu la requête, mais pour qui ? Il n’y a pas de request.user ici."

2. Le problème « backend ↔ backend » + « worker asynchrone (Celery) »
Ce qui m'a finalement poussé à introduire une clé API, c’est la communication backend‑to‑backend où un worker Celery intervient.
Voici le flux :
- L'utilisateur envoie une requête web
- Le back‑end place une « tâche » dans la file
- Le worker Celery consomme la file et envoie une requête asynchrone vers un serveur de calcul ou un autre back‑end
Dans ce scénario, les points douloureux sont :
- le worker n’a pas de request.user
- il n’y a pas de session (pas de navigateur)
- JWT devient compliqué : qui crée le token, où le stocke‑t‑il, comment le transmet ?
- OAuth2 repose sur une interaction utilisateur, donc impossible à appliquer ici
Quand JWT et la session deviennent inutilisables, la question se résume à :
« Comment le worker peut‑il indiquer quel client (tenant/utilisateur) doit être considéré comme l’initiateur de la tâche ? »
3. Dans le monde du worker, l’identification prime sur l’authentification
Dans une requête web, authentification = connexion et connexion = utilisateur sont naturellement liés.
Un worker, en revanche, n’est pas une personne ; c’est une application qui consomme du CPU de façon autonome. Avant de parler d’authentification, il faut d’abord être capable d’identifier qui déclenche le travail. L’authentification entre serveurs peut simplement s’appuyer sur un HMAC ou une clé secrète.
- Le travail doit s’exécuter avec les données du client A
- Le résultat doit être stocké dans les ressources du client A
- La facturation, les quotas et les permissions sont calculés pour le client A
Essayer de forcer JWT ou une session dans ce contexte gonfle la complexité (émission, stockage, transmission, expiration, renouvellement) et crée un malaise : « Comment un serveur peut‑il demander un token JWT à un client qui n’est même pas en train d’utiliser son navigateur ? » – une idée qui paraît tout simplement inacceptable. J’ai donc immédiatement abandonné cette piste.
4. Solution : la clé API était simple et puissante à ce stade
J’ai donc introduit une clé API, qui a résolu le problème d'un seul coup.
- Le worker envoie une requête interne avec un seul en‑tête qui assure à la fois l’authentification et l’identification
- Le serveur qui reçoit la clé peut mapper immédiatement la requête à un utilisateur/client
- La rotation ou la révocation de la clé devient triviale
Par exemple :
POST /v1/ai/jobs
Authorization: Api-Key <KEY>
Content-Type: application/json
{ "job_id": "...", "payload": {...} }
Le worker n’a pas besoin de request.user. Le back‑end qui reçoit la requête utilise simplement la clé API pour déterminer quel utilisateur est à l’origine.
5. Amélioration décisive : lier la clé API à l’USER simplifie l’opération
Ce qui m’a vraiment séduit, c’est la façon dont j’ai pu associer chaque clé à un utilisateur.
Les bibliothèques comme rest_framework_api_key offrent déjà la génération de clés, mais dans mon cas, l’enjeu était « clé ↔ utilisateur (client) ».
J’ai donc sous‑classé AbstractAPIKey en CustomAPIKey et ajouté une FK vers le modèle 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) # distinction entre clé de test et de prod
Cette liaison ouvre bien plus que de la simple authentification : elle débloque toute une gamme de fonctionnalités opérationnelles.
6. Gains opérationnels grâce à la génération automatique de clés par utilisateur
En créant automatiquement une clé lors de l’inscription d’un utilisateur, plusieurs avantages sont apparus.
1) Gestion de la validité simplifiée
- Recherche, désactivation ou suppression d’une clé spécifique à un utilisateur deviennent triviales
- La suppression d’un compte entraîne automatiquement la purge des clés associées (cascade FK)
2) Rotation de clé facilitée
- En cas de suspicion de fuite, on génère une nouvelle clé et on révoque l’ancienne en un clic
- Autoriser plusieurs clés permet un remplacement sans interruption (déploiement de la nouvelle clé puis retrait de l’ancienne)
3) Facturation, quotas et permissions centrés sur l’utilisateur
- On applique les limites au niveau de l’utilisateur, pas au niveau de la clé
- Plus besoin d’inférer « à qui appartient cette clé ? » à chaque appel
4) Possibilité d’attribuer plusieurs clés à un même utilisateur
Le champ is_test prend tout son sens. Comme la relation est une FK et non un OneToOne, on peut associer plusieurs clés à un même compte selon le contexte :
- Un utilisateur peut disposer d’une clé staging et d’une clé production simultanément
- Le flux de développement et celui de production restent clairement séparés
- Les logs et la surveillance peuvent distinguer le trafic test du trafic réel
7. Le choix de la méthode d’authentification dépend du contexte, pas d’une hiérarchie
En résumé, voici les combinaisons que je privilégie selon les situations :
- OAuth2 : intégrations tierces où le consentement utilisateur est essentiel
- Session : applications Django monolithiques où rapidité de développement et simplicité sont prioritaires
- JWT : architectures front‑end / back‑end séparées, applications mobiles ou SPA
- Clé API : communications backend‑to‑backend, automatisation, workers, batch jobs – tout ce qui n’est pas initié directement par un utilisateur
Lorsque le worker Celery entre en jeu, tenter d’unifier tout le monde sous une authentification « login‑based » ne fait qu’alourdir l’architecture. La clé API offre alors une échappatoire propre.
8. Conclusion
Les humains (navigateurs, applications) sont naturellement gérés avec sessions, JWT ou OAuth2.
Les workers, en revanche, sont des processus ; ils doivent simplement savoir pour quel client ils exécutent une tâche.
J’ai migré vers les clés API non pas pour des raisons de sécurité théorique, mais parce que c’était la solution la plus simple à ce stade du flux. En les liant à l’utilisateur, la gestion des clés devient un levier opérationnel, pas un simple mécanisme d’authentification.
Utilisez‑vous régulièrement les clés API ? La méthode présentée ici n’est qu’une facette de leur praticité, mais j’espère qu’elle inspirera vos propres implémentations.
Articles liés