Ein JWT (JSON Web Token) ist ein String aus drei base64url-Teilen, header.payload.signature, den ein Server einem Nutzer beim Login gibt und bei jeder späteren Anfrage prüft. Die Signatur beweist, dass der Server das Token ausgestellt hat und niemand es verändert hat, aber die Payload ist nur kodiert, nicht verschlüsselt, jeder kann sie also lesen. In Node.js erstellst du eines mit new SignJWT(payload).sign(secret) aus dem Paket jose und prüfst es mit jwtVerify(token, secret).
Es gibt 3 parts aus, dann user-42 admin: jwtVerify hat Signatur und Ablauf geprüft und die Payload zurückgegeben. Ändere das an jwtVerify übergebene Secret um ein Zeichen und führ es noch einmal aus: Die Prüfung schlägt mit signature verification failed fehl.
Was in einem JWT steckt
Header und Payload sind JSON, kodiert mit base64url. Zum Dekodieren braucht man keinen Schlüssel, deshalb darf ein JWT nie ein Passwort oder etwas anderes Geheimes enthalten.
Die ersten beiden Zeilen geben {"alg":"HS256"} und {"name":"Ada","role":"editor"} aus, lesbar für jeden, der das Token hat. Die Signatur (43 Zeichen bei HS256) verhindert, dass jemand es verändert: Ändere editor in admin, und die Signatur passt nicht mehr.
| Teil | Enthält | Geheim? |
|---|---|---|
| Header | Den Algorithmus, etwa HS256 | Nein, lesbar |
| Payload | Claims: sub (Nutzer-ID), exp (Ablauf), iat (ausgestellt am), deine eigenen Felder | Nein, lesbar |
| Signatur | HMAC- oder RSA-Signatur der ersten beiden Teile | Zum Erstellen braucht man den Schlüssel |
Ablauf und fehlgeschlagene Prüfung
setExpirationTime('15m') schreibt einen exp-Claim, und jwtVerify lehnt ein Token danach ab. Jede Ablehnung ist ein Fehler mit einem code, du kannst also ein abgelaufenes Token von einem gefälschten unterscheiden.
Die erste Prüfung schlägt mit ERR_JWT_EXPIRED fehl, die zweite, deren Signatur verändert wurde, mit ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Für deine API bedeuten beide dasselbe (antworte mit 401), aber den Code zu loggen hilft beim Debuggen.
Eine Express-Route mit einem Bearer-Token schützen
Der Client meldet sich einmal an, behält das Token und schickt es im Header Authorization als Bearer <token>. Eine Middleware prüft es und legt die Payload für die Route in req.user.
Die erste Anfrage, GET /me ohne Token, bekommt 401 und Missing token. Sende POST /login mit dem Body {"username": "ada", "password": "lovelace"}, kopier das token aus der Antwort und sende dann noch einmal GET /me mit dem Header Authorization: Bearer <token>: Die Antwort ist {"id":"user-1","name":"ada"}.
Das Paket jsonwebtoken
jsonwebtoken (npm install jsonwebtoken) ist die andere verbreitete Wahl. Seine Funktionen sign und verify sind synchron (mit optionalen Callbacks), statt Promises zurückzugeben:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
subject: 'user-42',
expiresIn: '1h',
});
const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid
Nimm für neuen Code jose: Es hat keine Abhängigkeiten und läuft auch in Edge-Runtimes und Browsern. Tokens beider Bibliotheken sind Standard-JWTs, die eine kann also prüfen, was die andere signiert hat.
Häufige Fehler
decode statt verify verwenden. jwt.decode und decodeJwt lesen die Payload, ohne die Signatur zu prüfen, ein gefälschtes Token geht also durch. Vertrau nur dem, was verify zurückgibt.
Ein kurzes oder eingechecktes Secret. Ein erratbares HS256-Secret lässt Angreifer eigene Tokens signieren. Nimm mindestens 32 zufällige Bytes, lies sie aus process.env und committe sie nie.
Tokens, die nie ablaufen. Ohne exp funktioniert ein geleaktes Token für immer. Setz immer einen Ablauf.
Häufig gestellte Fragen
Wo sollte der Client ein JWT speichern?
In einer Browser-App ist ein HttpOnly-, Secure-Cookie am sichersten, weil Skripte der Seite es nicht lesen können. localStorage ist einfacher, aber jedes Skript auf der Seite, auch ein eingeschleustes, kann das Token lesen. Mobile Apps nutzen den sicheren Speicher der Plattform.
Wie melde ich einen Nutzer mit JWTs ab?
Der Server kann ein Token, das er schon signiert hat, nicht zurückziehen; es bleibt gültig, bis es abläuft. Lösch es auf dem Client, halte Access Tokens kurzlebig, und wenn du sofortiges Abmelden brauchst, führ serverseitig eine Liste widerrufener Token-IDs (jti) und prüf sie bei jeder Anfrage.
HS256 oder RS256?
HS256 nutzt ein gemeinsames Secret zum Signieren und Prüfen, das passt zu einem einzelnen Backend. RS256 signiert mit einem privaten Schlüssel und prüft mit einem öffentlichen, so können andere Services Tokens prüfen, ohne sie erstellen zu können.
Wie lange sollte ein JWT gültig sein?
Access Tokens sind meist 5 bis 60 Minuten gültig. Für längere Sitzungen kombinierst du sie mit einem sorgfältiger gespeicherten Refresh Token, das der Client gegen ein neues Access Token tauscht.