API-Authentifizierung in PHP: API-Keys, JWT und OAuth2 richtig einsetzen
AI generated
<?php
8.4
PHP · API Design · Authentifizierung · Security
API-Authentifizierung in PHP
API-Keys, JWT und OAuth2 richtig einsetzen

Die Wahl des richtigen Authentifizierungsmechanismus entscheidet über Sicherheit und Integrationsaufwand einer PHP-API. Dieser Artikel zeigt, wann API-Keys ausreichen, wie JWT-Signaturen tatsächlich funktionieren und wann sich der zusätzliche Aufwand von OAuth2 wirklich lohnt, mit sicherer Speicherung und vollständigen Codebeispielen.

19 Min. Lesezeit API-Keys · JWT · OAuth2 · Bearer Token PHP 8.4

1. Warum Authentifizierung eine frühe Design-Entscheidung ist

Die Wahl des richtigen Mechanismus für API-Authentifizierung gehört zu den Entscheidungen, die sich in einer PHP-API später nur schwer nachträglich ändern lassen, weil jeder Client bereits gegen das gewählte Schema integriert. Wer zu früh auf einen komplexen OAuth2-Flow setzt, obwohl ein einfacher API-Key für den tatsächlichen Anwendungsfall gereicht hätte, produziert unnötigen Integrationsaufwand für alle Konsumenten. Wer umgekehrt zu lange bei simplen API-Keys bleibt, obwohl mehrere Nutzerrollen und delegierte Berechtigungen nötig sind, baut Sicherheitslücken durch improvisierte Rollenlogik.

Die drei in der Praxis dominierenden Ansätze für API-Authentifizierung sind statische API-Keys, signierte JWT-Tokens und der vollständige OAuth2-Autorisierungsrahmen. Jeder dieser Mechanismen löst ein anderes Problem, und keiner ist grundsätzlich der bessere, die richtige Wahl hängt von der Anzahl der Client-Typen, der Notwendigkeit delegierter Zugriffe und der gewünschten Kontrolle über Ablaufzeiten und Widerruf ab.

Dieser Artikel geht alle drei Mechanismen im Detail durch, mit besonderem Fokus darauf, wie sie in PHP korrekt implementiert werden, inklusive der häufigsten Sicherheitsfehler, die bei einer eigenen API-Authentifizierung entstehen, wenn Details wie Hashing, Signaturprüfung oder Ablaufzeiten übersehen werden.

2. API-Keys: einfachste Form, Einsatzbereiche und Grenzen

Ein API-Key ist die einfachste Form der API-Authentifizierung: eine lange, zufällige Zeichenkette, die der Client bei jeder Anfrage im Header mitschickt, meist als Authorization: Bearer oder in einem eigenen X-API-Key-Header. Der Server prüft den Key gegen eine gespeicherte Liste und identifiziert darüber den Aufrufer. Dieses Modell eignet sich hervorragend für Server-zu-Server-Kommunikation, bei der ein einzelner Client mit einer festen Identität arbeitet, etwa ein Partnersystem, das regelmäßig Daten synchronisiert.

Die Grenzen von API-Keys zeigen sich, sobald mehrere Nutzer über denselben Client agieren oder Zugriffsrechte zeitlich begrenzt und delegiert werden müssen. Ein API-Key hat üblicherweise keine eingebaute Ablaufzeit und keine granularen Berechtigungen, ein kompromittierter Key bleibt gültig, bis er manuell widerrufen wird. Für Endnutzer-Anwendungen mit Login-Flow ist ein API-Key daher meist die falsche Wahl, für interne Service-zu-Service-Kommunikation und einfache Partner-Integrationen bleibt er dagegen die pragmatischste Lösung.

3. API-Keys sicher generieren, hashen und speichern

Ein häufiger Fehler bei selbst gebauter API-Authentifizierung mit API-Keys: der Key wird im Klartext in der Datenbank gespeichert. Genau wie Passwörter sollten API-Keys niemals im Klartext persistiert werden, denn ein Datenbank-Leak würde sonst alle Keys sofort kompromittieren. Die Lösung ist, den Key beim Erzeugen als kryptographisch sicheren Zufallswert zu generieren, dem Client einmalig im Klartext zu zeigen, und in der Datenbank nur den Hash zu speichern.

Für den Hash reicht bei API-Keys, anders als bei Passwörtern, ein schneller kryptographischer Hash wie SHA-256 aus, weil der Key selbst bereits hohe Entropie besitzt und nicht gegen Brute-Force-Wörterbuchangriffe geschützt werden muss wie ein von Menschen gewähltes Passwort. Wichtig ist trotzdem ein zeitkonstanter Vergleich beim Prüfen des Hashes, um Timing-Angriffe zu vermeiden.


<?php

declare(strict_types=1);

/**
 * Generates and verifies API keys, storing only their hash.
 */
final class ApiKeyManager
{
    public function __construct(private readonly \PDO $pdo)
    {
    }

    /**
     * Generates a new key, returns the plaintext once for the client.
     */
    public function generate(int $clientId): string
    {
        $plaintext = 'msk_' . bin2hex(random_bytes(24));
        $hash = hash('sha256', $plaintext);

        $stmt = $this->pdo->prepare(
            'INSERT INTO api_keys (client_id, key_hash, created_at) VALUES (:client_id, :hash, NOW())'
        );
        $stmt->execute(['client_id' => $clientId, 'hash' => $hash]);

        return $plaintext; // shown to the client exactly once
    }

    public function verify(string $providedKey): ?int
    {
        $hash = hash('sha256', $providedKey);

        $stmt = $this->pdo->prepare(
            'SELECT client_id FROM api_keys WHERE key_hash = :hash AND revoked_at IS NULL'
        );
        $stmt->execute(['hash' => $hash]);
        $clientId = $stmt->fetchColumn();

        return $clientId !== false ? (int) $clientId : null;
    }
}

4. JWT: Aufbau, Signatur und zustandslose Verifikation

JWT, JSON Web Token, löst ein Problem, das API-Keys nicht abdecken: zustandslose API-Authentifizierung ohne Datenbankabfrage bei jeder Anfrage. Ein JWT besteht aus drei Base64-URL-kodierten Teilen, getrennt durch Punkte: Header mit Algorithmus-Angabe, Payload mit den eigentlichen Claims wie Nutzer-ID und Ablaufzeit, und Signatur. Die Signatur wird über Header und Payload mit einem geheimen Schlüssel berechnet, sodass jede Manipulation am Inhalt die Signaturprüfung fehlschlagen lässt.

Der entscheidende Vorteil gegenüber API-Keys: der Server muss den Token nicht in einer Datenbank nachschlagen, die Signaturprüfung allein reicht aus, um Authentizität und Unversehrtheit zu bestätigen. Das macht JWT besonders attraktiv für Systeme mit mehreren zustandslosen Backend-Instanzen, die keine gemeinsame Session-Datenbank teilen wollen. Der Nachteil: ein einmal ausgestellter Token lässt sich vor Ablauf nicht ohne zusätzliche Infrastruktur wie eine Sperrliste widerrufen.


{
  "header": { "alg": "HS256", "typ": "JWT" },
  "payload": {
    "sub": "user-4821",
    "role": "customer",
    "iat": 1785484800,
    "exp": 1785488400
  }
}

5. JWT in PHP validieren ohne Library-Blackbox

Auch wenn produktive PHP-APIs für JWT-Verarbeitung meist eine geprüfte Library wie firebase/php-jwt einsetzen sollten, hilft ein Blick auf die manuelle Implementierung, um zu verstehen, was diese Libraries tatsächlich tun. Die Validierung besteht aus drei Schritten: Header und Payload dekodieren, die Signatur mit dem geheimen Schlüssel neu berechnen und mit der übermittelten Signatur zeitkonstant vergleichen, und schließlich die Ablaufzeit im exp-Claim prüfen.

Ein häufiger Sicherheitsfehler bei selbst gebauter JWT-Verifikation: der Algorithmus aus dem Header wird blind übernommen, statt fest im Server-Code vorzugeben, welcher Algorithmus erwartet wird. Ein Angreifer könnte sonst den Algorithmus im Header auf none setzen und eine unsignierte Nutzlast einschleusen, ein real dokumentierter Angriff gegen unsauber implementierte JWT-Bibliotheken.


<?php

declare(strict_types=1);

/**
 * Minimal, educational JWT verification for HS256 tokens.
 * Production code should use a vetted library instead.
 */
final class JwtVerifier
{
    public function __construct(private readonly string $secret)
    {
    }

    /**
     * @return array<string, mixed> Decoded payload
     * @throws \RuntimeException On invalid signature or expired token
     */
    public function verify(string $token): array
    {
        [$headerB64, $payloadB64, $signatureB64] = explode('.', $token) + [null, null, null];

        if ($headerB64 === null || $payloadB64 === null || $signatureB64 === null) {
            throw new \RuntimeException('Malformed token.');
        }

        // Algorithm is fixed here, never trusted from the token header
        $expectedSignature = hash_hmac('sha256', "{$headerB64}.{$payloadB64}", $this->secret, true);
        $expectedB64 = rtrim(strtr(base64_encode($expectedSignature), '+/', '-_'), '=');

        if (!hash_equals($expectedB64, $signatureB64)) {
            throw new \RuntimeException('Invalid signature.');
        }

        $payload = json_decode(base64_decode(strtr($payloadB64, '-_', '+/')), true);

        if (($payload['exp'] ?? 0) < time()) {
            throw new \RuntimeException('Token expired.');
        }

        return $payload;
    }
}

6. OAuth2: Grundbegriffe und wann es wirklich gebraucht wird

OAuth2 ist kein Authentifizierungsprotokoll im engeren Sinn, sondern ein Autorisierungsrahmen für delegierten Zugriff: eine Anwendung erhält Zugriff auf Ressourcen im Namen eines Nutzers, ohne dessen Passwort jemals zu sehen. Zentrale Begriffe sind der Resource Owner, der Client, der Authorization Server und der Resource Server. Der Client erhält nach erfolgreicher Autorisierung ein Access Token, häufig als JWT umgesetzt, und optional ein Refresh Token für die langfristige Erneuerung.

OAuth2 lohnt sich fast ausschließlich dann, wenn tatsächlich Drittanbieter-Anwendungen im Namen von Nutzern auf eine API zugreifen sollen, etwa wenn externe Partner-Apps Bestellungen im Namen eines Shop-Kunden abrufen dürfen. Für eine interne API, bei der Client und Server demselben Unternehmen gehören, ist der Implementierungsaufwand von OAuth2 in den meisten Fällen unverhältnismäßig hoch, hier reichen einfachere Mechanismen wie API-Keys oder direkt ausgestellte JWTs völlig aus.

7. Token-Refresh und Ablaufzeiten richtig gestalten

Kurze Ablaufzeiten für Access Tokens reduzieren den Schaden bei einem gestohlenen Token erheblich, erzeugen aber häufigere Anfragen zum Erneuern. Die etablierte Lösung: kurzlebige Access Tokens, typischerweise 15 Minuten, kombiniert mit langlebigen Refresh Tokens, die ausschließlich für den Refresh-Endpunkt gültig sind und serverseitig widerrufbar bleiben, weil sie im Gegensatz zu Access Tokens in einer Datenbank gespeichert werden.

Ein Refresh Token sollte bei jeder Nutzung rotiert werden, das heißt, ein neues Refresh Token wird ausgestellt und das alte sofort ungültig gemacht. Wird ein bereits verwendetes, altes Refresh Token erneut vorgelegt, deutet das auf einen gestohlenen Token hin, und die API sollte in diesem Fall sicherheitshalber die gesamte Token-Familie widerrufen, statt nur die einzelne Anfrage abzulehnen.

8. Authentifizierung und Autorisierung sauber trennen

Ein häufiger Designfehler vermengt Authentifizierung, die Frage "wer ist der Aufrufer", mit Autorisierung, der Frage "was darf dieser Aufrufer tun". API-Keys, JWT und OAuth2 lösen ausschließlich das erste Problem, die Identifikation des Aufrufers. Rollen, Berechtigungen und ressourcenspezifische Zugriffsregeln gehören in eine separate Autorisierungsschicht, die nach erfolgreicher Authentifizierung greift und unabhängig vom gewählten Authentifizierungsmechanismus austauschbar bleibt.

Diese Trennung zahlt sich vor allem dann aus, wenn sich der Authentifizierungsmechanismus später ändert, etwa eine Migration von API-Keys zu OAuth2. Bleibt die Autorisierungslogik als eigene Schicht bestehen, betrifft die Migration ausschließlich, wie die Identität des Aufrufers ermittelt wird, nicht, welche Berechtigungen daraus abgeleitet werden.

9. API-Keys vs. JWT vs. OAuth2 im Vergleich

Die folgende Tabelle vergleicht die drei Mechanismen entlang der wichtigsten Entscheidungskriterien.

Kriterium API-Key JWT OAuth2
Implementierungsaufwand Sehr gering Moderat Hoch
Zustandslose Prüfung Nein, DB-Abfrage nötig Ja, nur Signaturprüfung Ja, Access Token wie JWT
Delegierter Nutzerzugriff Nicht vorgesehen Nur mit zusätzlicher Logik Kernfunktion
Widerruf vor Ablauf Einfach per DB-Flag Nur mit Sperrliste Über Refresh-Token-Widerruf
Typischer Einsatz Server-zu-Server, Partner Eigene Mobile/Web-Clients Drittanbieter-Integrationen

In der Praxis kombinieren viele PHP-APIs mehrere Mechanismen: API-Keys für Server-zu-Server-Integrationen, JWT für den eigenen mobilen Client, und OAuth2 ausschließlich für die Handvoll Drittanbieter-Partner, die tatsächlich delegierten Zugriff benötigen.

Mironsoft

PHP-API-Security und Authentifizierungsarchitektur

Die richtige Authentifizierung für eure PHP-API?

Wir analysieren euren Anwendungsfall, wählen zwischen API-Keys, JWT und OAuth2 die passende Lösung und setzen sichere Speicherung, Signaturprüfung und Token-Refresh produktionsreif um.

Security-Audit

Bestehende Authentifizierungslogik auf Schwachstellen prüfen

JWT/OAuth2-Einführung

Signaturprüfung, Ablaufzeiten und Refresh-Flows sauber implementieren

Autorisierungsschicht

Rollen und Berechtigungen sauber von der Authentifizierung trennen

10. Zusammenfassung

API-Authentifizierung in PHP ist keine Ein-Größe-passt-allen-Entscheidung: API-Keys bleiben die pragmatische Wahl für Server-zu-Server-Kommunikation mit fester Identität, JWT bringt zustandslose Verifikation für eigene mobile und Web-Clients, und OAuth2 lohnt sich fast ausschließlich für delegierten Zugriff durch echte Drittanbieter. Wer diese drei Mechanismen nach ihrem tatsächlichen Einsatzzweck statt nach Trend wählt, vermeidet sowohl unnötigen Implementierungsaufwand als auch unpassende Sicherheitslücken.

Unabhängig vom gewählten Mechanismus gelten dieselben Grundregeln: Keys und Secrets niemals im Klartext speichern, Signaturen immer zeitkonstant vergleichen, den erwarteten Algorithmus fest im Server-Code verankern statt aus dem Token zu übernehmen, und Authentifizierung strikt von Autorisierung trennen. Diese Grundlagen entscheiden am Ende mehr über die Sicherheit einer PHP-API als die Wahl zwischen API-Key, JWT und OAuth2 selbst.

API-Authentifizierung in PHP: Das Wichtigste auf einen Blick

API-Keys

Nur gehasht speichern, ideal für Server-zu-Server, keine granularen Berechtigungen oder Ablaufzeit eingebaut.

JWT

Zustandslose Signaturprüfung statt DB-Abfrage, Algorithmus immer serverseitig fest vorgeben, nie aus dem Header übernehmen.

OAuth2

Nur bei echtem delegiertem Drittanbieter-Zugriff einsetzen, sonst unverhältnismäßig hoher Aufwand.

Refresh-Strategie

Kurzlebige Access Tokens plus rotierende, widerrufbare Refresh Tokens für den richtigen Kompromiss aus Sicherheit und Nutzbarkeit.

11. FAQ: API-Authentifizierung in PHP

1Wann reicht ein API-Key aus?
Bei Server-zu-Server-Kommunikation mit fester Identität ohne delegierten Zugriff oder mehrere Rollen.
2API-Keys im Klartext speichern?
Nein, gehasht wie Passwörter speichern. SHA-256 reicht wegen der hohen Entropie der Keys.
3Vorteil von JWT gegenüber API-Keys?
Zustandslose Verifikation über Signaturprüfung, keine Datenbankabfrage pro Anfrage nötig.
4Warum Algorithmus nicht aus dem Header übernehmen?
Angreifer könnten den Algorithmus auf none setzen. Der erwartete Algorithmus muss serverseitig fest sein.
5Was ist OAuth2 im Kern?
Ein Autorisierungsrahmen für delegierten Zugriff im Namen eines Nutzers, kein reines Auth-Protokoll.
6Wann lohnt sich OAuth2?
Bei echten Drittanbieter-Zugriffen im Namen von Nutzern, nicht bei internen APIs im selben Unternehmen.
7Wie lange sollten Access Tokens gelten?
Kurz, etwa 15 Minuten, kombiniert mit langlebigen, widerrufbaren Refresh Tokens.
8Was ist Refresh-Token-Rotation?
Bei jeder Nutzung wird ein neues Refresh Token ausgestellt, das alte sofort ungültig gemacht.
9Warum Authentifizierung und Autorisierung trennen?
Weil sie unterschiedliche Fragen beantworten und die Trennung Migrationen des Mechanismus vereinfacht.
10Können mehrere Mechanismen kombiniert werden?
Ja, häufig API-Keys für Server-zu-Server, JWT für eigene Clients, OAuth2 nur für echte Drittanbieter.