JWT verstehen und selbst validieren

JSON Web Tokens sind überall. Viele nutzen sie ohne wirklich zu verstehen wie sie funktionieren.
Das rächt sich wenn man die Sicherheit falsch einschätzt.

Aufbau

Ein JWT besteht aus drei Base64url-kodierten Teilen getrennt durch Punkte.

header.payload.signature

Header enthält Algorithmus und Typ.

{"alg": "HS256", "typ": "JWT"}

Payload enthält die Claims.

{"sub": "42", "role": "admin", "exp": 1735689600}

Signatur ist HMAC über Header + Payload mit dem Secret.

Was ein JWT garantiert und was nicht

Die Signatur stellt sicher dass der Inhalt nicht verändert wurde.
Der Inhalt ist aber nur Base64-kodiert, nicht verschlüsselt — jeder kann ihn lesen.
Keine sensiblen Daten in den Payload.

In PHP validieren — ohne Bibliothek

function validateJwt(string $token, string $secret): ?array
{
    $parts = explode('.', $token);
    if (count($parts) !== 3) return null;

    [$headerB64, $payloadB64, $sigB64] = $parts;

    $expected = base64url_encode(hash_hmac('sha256', "$headerB64.$payloadB64", $secret, true));
    if (!hash_equals($expected, $sigB64)) return null;

    $payload = json_decode(base64url_decode($payloadB64), true);
    if (!$payload) return null;

    if (isset($payload['exp']) && $payload['exp'] < time()) return null;

    return $payload;
}

function base64url_encode(string $data): string {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode(string $data): string {
    return base64_decode(strtr($data, '-_', '+/'));
}

Wann JWT, wann Sessions

Sessions sind für Web-Apps mit Browser oft die bessere Wahl — serverseitig invalidierbar, kein Token im localStorage der per XSS gestohlen werden kann.

JWT macht Sinn für APIs die von mehreren Services genutzt werden und für mobile Clients.

Das alg:none Problem

Einige ältere Bibliotheken akzeptieren "alg": "none" und überspringen die Signaturprüfung.
Beim selbst Validieren immer den Algorithmus explizit prüfen — nie aus dem Header übernehmen.