## 1. "Ik ben ingelogd, maar je kent me niet!" (Het begin van het probleem) {#sec-ffb74987f051} OAuth2, JWT, sessie‑authenticatie… er zijn talloze methoden, en voor de meeste toepassingen volstaat één ervan. Ik heb ze allemaal gebruikt. * Toen ik OAuth2 toevoegde aan mijn eigen e‑mailclient of aan ChatGPT’s MyGPT, voelde het meteen als een **echte gebruikerservaring**. * Een monolithische Django‑applicatie werkt het best met **sessie‑authenticatie**. * Bij een gescheiden front‑/backend‑architectuur is **JWT** vaak de schoonste oplossing. Maar op een gegeven moment stortte deze combinatie in één klap in. De dader? **Asynchrone taken (Celery)**. Wanneer een gebruiker op een knop drukt, voert de backend het werk niet zelf uit, maar stuurt het door naar een AI‑rekenserver of een worker op afstand. De worker vraagt zich dan af: > "Ik heb een verzoek ontvangen, maar voor wie? Er is geen request.user beschikbaar." ![Robot‑worker die een brief met een API‑key overhandigt](/media/editor_temp/6/b63803ff-21ba-4f1b-a5af-47c8ca0fdd25.png) ## 2. Het probleem: "backend ↔ backend" + "asynchrone worker (Celery)" {#sec-a7eab2678145} De doorslaggevende reden om een API‑key te introduceren was de **communicatie tussen backends waarbij een Celery‑worker tussenkomt**. In plaats van dat een klik direct naar de AI‑rekenserver gaat, verloopt het als volgt: 1. De gebruiker stuurt een web‑verzoek. 2. De backend plaatst een "taak" in de queue. 3. Een Celery‑worker haalt de taak op en stuurt een **asynchroon verzoek** naar een backend‑/rekenserver. Het knelpunt was duidelijk: * De worker heeft **geen request.user**. * Er is geen sessie (het is tenslotte geen browser). * JWT is onhandig – wie beheert en levert het token, en waar? * OAuth2 vereist gebruikersinteractie en is daardoor onbruikbaar. Zodra JWT en sessies hun nut verliezen, blijft één vraag over: > "Hoe kan de worker aangeven welke klant (tenant/user) de asynchrone taak uitvoert?" ## 3. In de wereld van workers draait het eerst om identificatie, niet om authenticatie {#sec-77f5863a0a93} Voor een web‑verzoek betekent "authenticatie = login" en "login = gebruiker". Een worker is echter **een applicatie‑proces**, geen mens, dus *identificatie* gaat voor. * De taak moet uitgevoerd worden met de data van klant A. * Het resultaat moet opgeslagen worden onder klant A. * Facturatie, rechten en quota moeten aan klant A worden toegerekend. Proberen dit met JWT of sessies te forceren leidt tot een complexe token‑levenscyclus en een oncomfortabel gevoel: "Waarom zou een backend een JWT voor een niet‑browser‑gebruiker uitgeven?" Deze gedachte werd direct verworpen. ## 4. Oplossing: Een API‑key is in dit scenario simpel én krachtig {#sec-2110738d8481} We kozen dus voor een **API‑key**. Het lost alles in één klap op: * De worker stuurt één header mee – zowel authenticatie als identificatie. * De server kan die key direct koppelen aan een specifieke gebruiker/klant. * Rotatie of intrekking van de key is helder en beheersbaar. Voorbeeld van een verzoek van de worker naar de rekenserver: ```http POST /v1/ai/jobs Authorization: Api-Key Content-Type: application/json { "job_id": "...", "payload": {...} } ``` De worker heeft geen `request.user` nodig; de ontvangende backend kan met de meegezonden API‑key de gebruiker bepalen. ## 5. Door de API‑key te koppelen aan een USER werd beheer een eitje {#sec-762242363071} Wat mij vooral beviel, was dat bestaande bibliotheken zoals `rest_framework_api_key` wel API‑keys leveren, maar niet per se een **"key ↔ gebruiker"‑relatie**. Daarom stelden we een eigen `CustomAPIKey` model op dat via een foreign key naar `AUTH_USER_MODEL` verwijst. ```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) # onderscheid test‑/stage‑keys ``` Zo krijgen we niet alleen authenticatie, maar een volledige **operationele laag**. ## 6. Automatische key‑generatie per gebruiker levert operationele voordelen {#sec-d180d12a94f2} Door bij registratie een key aan de gebruiker toe te wijzen, profiteerden we van: ### 1) Eenvoudig beheer van geldigheid {#sec-bfe883fb0c50} * Opvragen, deactiveren of verwijderen van een specifieke gebruikers‑key wordt triviaal. * Bij accountverwijdering zorgt de FK‑cascade automatisch voor opruiming. ### 2) Makkelijke key‑rotatie {#sec-7f4bccda836d} * Bij een mogelijk lek kun je direct een nieuwe key uitgeven en de oude intrekken. * Meerdere keys per gebruiker maken **zero‑downtime** wissels mogelijk. ### 3) Facturatie, quota en permissies per gebruiker {#sec-1081bea2890b} * In plaats van per key, kun je limieten en tarieven op de **gebruiker** baseren. * De vraag "Welke gebruiker staat achter deze API‑key?" verdwijnt. ### 4) Meerdere keys per gebruiker {#sec-1bc8fa524379} Met een `is_test`‑vlag kun je bijvoorbeeld een **test‑key** en een **productie‑key** voor dezelfde gebruiker hebben. * Scheiding van stage‑ en productie‑verkeer wordt eenvoudig. * Monitoring kan onderscheid maken tussen test‑ en live‑verkeer. ## 7. Authenticatiemethoden: geen hiërarchie, maar context‑afhankelijke wapens {#sec-f73d5faf76f6} Samengevat, mijn huidige aanbevelingen per situatie zijn: * **OAuth2**: ideaal voor externe services en processen waarbij gebruikers expliciet toestemming geven. * **Sessies**: perfect voor een pure Django‑app – snel en simpel. * **JWT**: geschikt voor front‑/backend‑scheiding, mobiele apps of SPA’s. * **API‑key**: de onbetwiste winnaar voor backend‑to‑backend, automatisering, workers en batch‑processen. Zodra een Celery‑worker in het spel komt, maakt het vasthouden aan "login‑gebaseerde" authenticatie het systeem alleen maar ingewikkelder. Een API‑key biedt daar een elegante uitweg. ## 8. Conclusie {#sec-d746e56908e8} Voor mensen (browsers/apps) zijn sessies, JWT en OAuth2 natuurlijk. Een worker is echter een proces dat **moet weten van wie de taak is**. Onze overstap naar API‑keys was niet gedreven door een groot security‑debat, maar door de wens om het simpelst mogelijke mechanisme te gebruiken. Door de key te koppelen aan een gebruiker verandert de key van een technisch hulpmiddel in een **operationele hefboom**. Gebruik jij al API‑keys? De hier beschreven aanpak toont slechts een fractie van hun voordelen. Hopelijk biedt dit verhaal een inspiratiepunt voor je eigen projecten. --- **Gerelateerde artikelen** - [HMAC‑handtekening voor server‑to‑server integriteit in Django/DRF](/ko/whitedec/2025/12/9/django-drf-hmac-signature-server-to-server-integrity/) - [React RCE‑incident: lessen over HMAC, key‑rotatie en zero‑trust](/ko/whitedec/2025/12/8/react-rce-lesson-hmac-key-rotation-zero-trust/)