## 1. "Je suis connecté, mais vous ne me reconnaissez pas" (début du problème) {#sec-ffb74987f051} 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." ![Le robot worker porte une clé API et remet une lettre](/media/editor_temp/6/b63803ff-21ba-4f1b-a5af-47c8ca0fdd25.png) ## 2. Le problème « backend ↔ backend » + « worker asynchrone (Celery) » {#sec-a7eab2678145} 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 : 1. L'utilisateur envoie une requête web 2. Le back‑end place une « tâche » dans la file 3. 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 {#sec-77f5863a0a93} 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 {#sec-2110738d8481} 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 : ```http POST /v1/ai/jobs Authorization: Api-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 {#sec-762242363071} 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`. ```python 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 {#sec-d180d12a94f2} En créant automatiquement une clé lors de l’inscription d’un utilisateur, plusieurs avantages sont apparus. ### 1) Gestion de la validité simplifiée {#sec-bfe883fb0c50} * 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 {#sec-7f4bccda836d} * 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 {#sec-1081bea2890b} * 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 {#sec-1bc8fa524379} 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 {#sec-f73d5faf76f6} 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 {#sec-d746e56908e8} 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** - [Protéger l’intégrité des requêtes serveur‑à‑serveur avec des signatures HMAC dans Django/DRF](/ko/whitedec/2025/12/9/django-drf-hmac-signature-server-to-server-integrity/) - [Leçons du RCE React : signatures HMAC, rotation de clés et zéro confiance](/ko/whitedec/2025/12/8/react-rce-lesson-hmac-key-rotation-zero-trust/)