JWT-Guides

So prüfe ich den JWT exp-Claim und den Ablauf

Wie ich exp als NumericDate lese, Sekunden und Millisekunden korrekt behandle, Verifier-Zeit und Clock Skew vergleiche und entscheide, ob Expiration wirklich die Ursache ist.

Von JWTDecoder.toolsZuletzt geprüft 2026-09-16

Redaktionelle Stimme: Formulierungen in der ersten Person beschreiben den dokumentierten Workflow von JWTDecoder.tools; Herausgeber und Organisationsautor ist JWTDecoder.tools.

JWT-Zeitachse mit iat, nbf, aktivem Zeitraum und exp
iat, nbf, aktives Zeitfenster und exp prüfen, bevor Ablauf als Ursache angenommen wird.

Wenn ich token expired, jwt expired oder einen zeitlich verdächtigen 401 sehe, prüfe ich exp früh, aber nicht isoliert. Ich konvertiere den Claim selbst, vergleiche ihn mit der Uhr des Verifiers und lese gleichzeitig iat und nbf. So trenne ich echte Expiration von Einheitenfehlern, Clock Skew und zusätzlichen Policy-Regeln.

Für eine schnelle Sichtung nutze ich den JWT Expiry Checker.

exp ist eine NumericDate in Sekunden

RFC 7519 definiert exp als Expiration Time. NumericDate zählt Sekunden seit Unix Epoch:

{
  "iat": 1789200000,
  "nbf": 1789200000,
  "exp": 1789203600
}

Das konzeptionelle Akzeptanzkriterium lautet:

current verifier time < exp

Sobald die Verifier-Zeit exp erreicht oder überschreitet, sollte ein Profil mit Expiration-Prüfung das Token ablehnen, abgesehen von ausdrücklich konfigurierter kleiner Clock Tolerance.

Der häufigste Implementierungsfehler: Sekunden gegen Millisekunden

JavaScript Date.now() liefert Millisekunden. JWT NumericDate liefert Sekunden:

const expiresAt = payload.exp * 1000;
const expired = Date.now() >= expiresAt;

Dieser Vergleich ist falsch:

const expired = Date.now() >= payload.exp; // falsche Einheit

In Logs erkenne ich den Fehler oft daran, dass ein zehnstelliger exp mit einem dreizehnstelligen JavaScript-Timestamp verglichen wird.

Wie ich exp in mehreren Laufzeiten prüfe

Node/Browser:

const now = Math.floor(Date.now() / 1000);
if (typeof payload.exp === "number" && now >= payload.exp) {
  throw new Error("token expired");
}

Python:

import time
if isinstance(payload.get("exp"), (int, float)) and time.time() >= payload["exp"]:
    raise ValueError("token expired")

Diese Beispiele erklären nur die Zeitlogik. Für echte Authentifizierung nutze ich eine Bibliothek, die Signatur, Algorithmus, Issuer und Audience ebenfalls erzwingt.

Clock Skew richtig einordnen

RFC 7519 erlaubt Implementierungen eine kleine Leeway für unvermeidbare Uhrdifferenzen. Ich nutze Toleranz nur für Randfälle, nicht als pauschale Verlängerung des Tokens.

Beispiel:

exp             12:00:00Z
verifier clock  12:00:03Z
leeway          5s
=> kann noch innerhalb der Toleranz liegen

Liegt exp dagegen 45 Minuten zurück, ist „Clock Skew“ keine sinnvolle Erklärung.

iat, nbf und exp gemeinsam lesen

Ich prüfe die Zeitachse:

issued -------- active window -------- expired
  iat     nbf                     exp

Auffällige Kombinationen sind zum Beispiel:

  • nbf > exp;
  • iat deutlich in der Zukunft;
  • ungewöhnlich lange Laufzeit zwischen iat und exp;
  • exp vor iat;
  • alle Zeitwerte um Faktor 1000 verschoben.

Der JWT Validator hilft mir, diese Claims zusammen mit der Signatur zu prüfen.

Fehlendes exp ist nicht dasselbe wie syntaktisch ungültig

Nicht jedes JWT-Profil schreibt exp vor. RFC 7523 verlangt es beispielsweise für das dort definierte OAuth-JWT-Profil, während RFC 7519 Claims grundsätzlich profilabhängig behandelt.

Deshalb frage ich:

  1. Welcher Token-Typ ist das?
  2. Welches Profil oder welche API-Policy gilt?
  3. Ist exp dort verpflichtend?
  4. Gibt es zusätzlich ein maximales Token-Alter?

Ich markiere „missing exp“ als Policy-Frage, nicht automatisch als kaputte JWT-Syntax.

Warum ein Token vor exp trotzdem abgelehnt werden kann

Ein zukünftiges exp beweist nicht, dass das Token akzeptiert werden darf. Andere Gründe sind:

  • Signatur ungültig;
  • nbf noch nicht erreicht;
  • falscher iss;
  • falscher aud;
  • falscher Token-Typ;
  • fehlende Scopes/Rollen;
  • Revocation oder Session-Policy.

Wenn die Zeit korrekt aussieht, wechsle ich bewusst die Hypothese und nutze den JWT Debugger.

Warum ich exp nicht im Payload „repariere“

Ein signiertes JWT schützt die ursprünglichen Header- und Payload-Bytes. Ändere ich:

{ "exp": 1789203600 }

zu einem späteren Wert, passt die alte Signatur nicht mehr. Ein legitimer Renewal-Prozess erzeugt ein neues Token mit neuer Signatur.

Meine Entscheidungslogik bei Zeitfehlern

BeobachtungInterpretationNächster Schritt
now >= expechte Expiration wahrscheinlichRefresh/Reauthentication
wenige Sekunden DifferenzClock Skew möglichServerzeit/Tolerance prüfen
nbf > nownoch nicht aktivIssuance/Clock prüfen
iat weit in ZukunftClock/Unit-FehlerUhren und Einheiten prüfen
exp fehltprofilabhängige PolicyToken-Profil lesen
exp in Zukunft, trotzdem 401andere ValidierungSignatur/iss/aud prüfen

Mein exp-Workflow

  1. Ich dekodiere das Token mit dem JWT Decoder.
  2. Ich bestätige, dass exp numerisch ist und in Sekunden interpretiert wird.
  3. Ich konvertiere exp zu UTC.
  4. Ich vergleiche mit der Uhr des tatsächlichen Verifiers.
  5. Ich prüfe iat und nbf auf Widersprüche.
  6. Ich kontrolliere eine explizite, kleine Clock Tolerance.
  7. Ich prüfe, ob das Token-Profil exp zwingend verlangt.
  8. Bei echter Expiration nutze ich den vorgesehenen Renewal-Flow.
  9. Ist das Token aktiv, prüfe ich Signatur, Issuer und Audience statt weiter an exp zu drehen.

Für mich beantwortet exp genau eine Frage: Liegt das Token außerhalb seines deklarierten Zeitfensters? Ob die Anwendung dem Token vertrauen und es akzeptieren darf, entscheidet erst die vollständige Validierung.

Quellen und Referenzen

Verwandte Guides