1. "Ich bin eingeloggt, warum kennt mich das System nicht?" (Der Auftakt)

OAuth2, JWT, Session‑Authentifizierung … es gibt unzählige Methoden, und in den meisten Fällen reicht eine davon völlig aus – das war auch meine Erfahrung.

  • Als ich meinem selbstgebauten E‑Mail‑Client oder MyGPT von ChatGPT OAuth2 hinzufügte, dachte ich: „Endlich ein echtes Nutzererlebnis.“
  • Für reine Django‑Web‑Apps ist Session‑Authentifizierung das Non‑plus‑ultra!
  • In einer Front‑/‑Back‑Trennung hat sich JWT als die sauberste Lösung erwiesen.

Doch genau in einer Situation brach dieses Setup plötzlich zusammen.

Der Schuldige war asynchrone Verarbeitung (Celery). Wenn ein Nutzer einen Button drückt, führt das Backend die Arbeit nicht selbst aus, sondern delegiert sie an einen externen KI-Rechenserver oder Worker. Und der Worker fragt nach:

"Entschuldigung … ich habe die Anfrage erhalten, aber für wen führe ich sie aus? request.user ist nicht vorhanden."

Roboter‑Worker mit API‑Key übergibt einen Brief

2. Das Problem: "Backend ↔ Backend" + "asynchroner Worker (Celery)"

Der entscheidende Auslöser für die Einführung von API‑Keys war die Architektur, bei der Backend‑Server untereinander kommunizieren und dabei ein Celery‑Worker eingeschaltet wird.

Der Ablauf sah so aus:

  1. Der Nutzer sendet eine Web‑Anfrage.
  2. Das Backend legt einen "Job" in die Queue.
  3. Der Celery‑Worker konsumiert die Queue und schickt asynchron eine Anfrage an einen anderen Backend‑/‑Rechen‑Server.

Hier traten die größten Hürden auf:

  • Im Worker gibt es kein request.user.
  • Es gibt keine Session (kein Browser).
  • JWT ist problematisch – wer verwaltet und übergibt das Token?
  • OAuth2 setzt eine Nutzerinteraktion voraus – damit ist es von vornherein nicht anwendbar.

Sobald JWT und Session ausscheiden, bleibt nur noch die Frage:

"Wie kann der Worker dem Rechen‑Server mitteilen, welcher Kunde (Tenant/User) die Aufgabe ausführt?"

3. Im Worker‑Universum geht es zuerst um Identifikation, nicht um Authentifizierung

Bei einer normalen Web‑Anfrage bedeutet Authentifizierung = Login = User. Ein Worker ist jedoch keine Person, sondern ein Prozess, der CPU‑Ressourcen „ausleiht“. Deshalb ist zuerst die Identifikation nötig; die eigentliche Authentifizierung zwischen Servern lässt sich mit HMAC‑ oder Secret‑Key‑Verfahren lösen.

  • Der Job muss mit den Daten von Kunde A ausgeführt werden.
  • Das Ergebnis muss in den Ressourcen von Kunde A gespeichert werden.
  • Quoten, Berechtigungen und Abrechnungen basieren auf Kunde A.

Versucht man, das mit JWT oder Sessions zu erzwingen, wächst die Komplexität (Token‑Ausgabe, -Speicherung, -Ablauf, -Erneuerung) und man bekommt das beklemmende Gefühl: „Wie soll unser Backend einem Nicht-Browser-User ein JWT ausstellen?“ – das war für mich ein klares No-Go.

4. Lösung: API‑Key – simpel und stark

Die Einführung von API‑Key hat das Problem auf einen Schlag gelöst:

  • Der Worker kann mit einem einzigen Header sowohl Authentifizierung als auch Identifikation übermitteln.
  • Der empfangende Server kann anhand des Schlüssels sofort den zugehörigen Nutzer/Kunden bestimmen.
  • Schlüssel lassen sich leicht zurückziehen oder rotieren.

Ein typisches Beispiel für die interne Anfrage sieht so aus:

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

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

Der Worker benötigt kein request.user. Der empfangende Backend‑Dienst nutzt den API‑Key, um den Nutzer zu ermitteln (wie im nächsten Abschnitt beschrieben).

5. Durchbruch: API‑Key mit User verknüpfen: Vereinfachte Betriebsführung

Was mich besonders überzeugte, war die Möglichkeit, den Schlüssel direkt mit dem User‑Modell zu verbinden. Die Bibliothek rest_framework_api_key liefert zwar Schlüssel, aber ich brauchte die Bindung Key ↔ User.

Dazu habe ich AbstractAPIKey erweitert und eine FK‑Beziehung zum AUTH_USER_MODEL aufgebaut:

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)  # Unterscheidung zwischen Stage‑ und Produktions‑Key

Damit öffnet sich ein ganzes Spektrum an Betriebs-Features.

6. Automatisierte Key‑Ausgabe pro User – operative Vorteile

Beim Anlegen eines Nutzers wird automatisch ein Schlüssel erzeugt. Das brachte mehrere Vorteile:

1) Einfache Verwaltung

  • Schlüssel eines Users können abgefragt, deaktiviert oder gelöscht werden.
  • Beim Löschen eines Users werden die zugehörigen Schlüssel über das FK‑Cascade automatisch entfernt.

2) Schlüssel-Rotation wird zum Kinderspiel

  • Bei Verdacht auf Kompromittierung einfach einen neuen Schlüssel ausgeben und den alten zurückziehen.
  • Mehrere Schlüssel erlauben Zero‑Downtime‑Wechsel (neuer Schlüssel aktiv, alter noch gültig, bis er ausläuft).

3) Abrechnung & Quoten auf Nutzer‑Basis

  • Statt pro Schlüssel zu rechnen, lässt sich die Nutzung direkt dem User zuordnen.
  • Die Frage "Welcher User hat diesen API‑Key verwendet?" entfällt.

4) Mehrere Schlüssel pro User

  • Das Flag is_test wird hier nützlich: Da die Beziehung FK und nicht One‑To‑One ist, können einem User mehrere Schlüssel für unterschiedliche Zwecke zugewiesen werden.
  • Beispiel: Stage‑Key und Production‑Key gleichzeitig.
  • So lassen sich Entwicklungs‑ und Produktions‑Traffic klar trennen (Logs, Monitoring).

7. Authentifizierung ist kein Wettbewerb, sondern ein Werkzeug‑Set für den jeweiligen Kontext

Zusammengefasst sieht meine aktuelle „Best‑of‑Breed“-Kombination folgendermaßen aus:

  • OAuth2 – ideal für externe Services & Nutzer‑Einwilligungen.
  • Session‑Auth – perfekt für reine Django‑Web‑Apps (schnell & unkompliziert).
  • JWT – gut für Front‑/‑Back‑Trennung, Mobile‑ und SPA‑Clients.
  • API‑Key – unschlagbar für Backend‑zu‑Backend, Automatisierung, Worker, Batch‑Jobs – also überall dort, wo kein Nutzer‑Login beteiligt ist.

Gerade wenn ein Celery‑Worker ins Spiel kommt, führt der Versuch, alles über "Login‑basierte Authentifizierung" zu vereinheitlichen, nur zu mehr Komplexität. Der API‑Key war hier der elegante Ausweg.

8. Fazit

Menschen (Browser/Apps) werden natürlich über Session, JWT oder OAuth2 behandelt. Ein Worker ist jedoch ein Prozess und muss zuordnen können, wessen Aufgabe er ausführt.

Ich habe mich für API‑Keys entschieden, nicht wegen grandioser Sicherheits‑Buzzwords, sondern weil sie das Problem in diesem speziellen Abschnitt am einfachsten lösten. Durch die Verknüpfung mit dem User‑Modell wurden Schlüssel zu einem Betriebs‑Hebel statt zu einem reinen Authentifizierungs‑Tool.

Nutzen Sie API‑Keys bereits? Meine Umsetzung zeigt nur einen Teil der Komfort‑Features – ich hoffe, sie inspiriert Sie zu eigenen iluminierenden Lösungen.


Verwandte Beiträge