Zum Inhalt springen
Europe/Berlin
--:--:--

JWT-Sicherheit 2026: 8 Regeln für gehärtete APIs

October 17, 2026

JSON Web Tokens gehören seit Jahren zum Standard für zustandslose Authentifizierung in modernen Microservice- und Cloud-Architekturen. Doch die scheinbare Einfachheit trügt: Kaum ein Authentifizierungsmechanismus wird in Backend-Systemen so häufig fehlerhaft implementiert wie JWTs. Fehlkonfigurationen in Validierungsbibliotheken, unbedachte Annahmen über Header-Werte und veraltete Spezifikationsmuster führen regelmäßig zu kritischen Schwachstellen bis hin zur vollständigen Übernahme administrativer Rechte.

Wer 2026 widerstandsfähige APIs betreibt, muss über das bloße Prüfen einer Signatur hinausgehen. Dieser Leitfaden fasst die wichtigsten Härtungsmaßnahmen für Go, Python und Node.js aus der Praxis zusammen.


Der Klassiker unter den JWT-Schwachstellen bleibt auch 2026 relevant: Algorithm Confusion. Bei asymmetrischen Verfahren (wie RS256 oder ES256) nutzt der Identity-Provider einen privaten Schlüssel zum Signieren, während die validierende API den öffentlichen Schlüssel zum Verifizieren verwendet.

Akzeptiert die Verifizierungslogik blind den im Token-Header deklarierten Algorithmus (alg), kann ein Angreifer das Token auf den symmetrischen Algorithmus HS256 umschreiben. Als HMAC-Secret nutzt er schlicht den ihm bekannten öffentlichen RSA-Schlüssel des Servers. Da der Server denselben öffentlichen Schlüssel als Verifizierungs-Secret in die HMAC-Funktion einspeist, ist die gefälschte Signatur mathematisch gültig.

Gegenmaßnahme: Algorithmus-Hard-Pinning. Der erwartete Algorithmus muss der Verifizierungsbibliothek fest vorgegeben werden – der Header-Wert darf niemals dynamisch bestimmen, welche Krypto-Logik greift.

Go
package auth

import (
	"crypto/rsa"
	"fmt"
	"github.com/golang-jwt/jwt/v5"
)

func VerifyToken(tokenString string, rsaPublicKey *rsa.PublicKey) (*jwt.Token, error) {
	return jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
		// Typprüfung gegen Algorithm Confusion
		if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("invalid algorithm: %v", t.Header["alg"])
		}
		return rsaPublicKey, nil
	},
		jwt.WithValidMethods([]string{"RS256"}),
		jwt.WithAudience("https://api.example.com"),
		jwt.WithIssuer("https://auth.example.com"),
	)
}

Der kid-Header (Key ID) dient dazu, bei Schlüsselrotationen den passenden Public Key auszuwählen. In der Praxis sieht man hier haarsträubende Fehler: Backend-Services übergeben kid direkt an Dateisystem-Pfade (/keys/${kid}.pem), an SQL-Queries (SELECT key FROM keys WHERE id = '${kid}') oder an Directory-Dienste.

Die Folge sind Path-Traversal-Angriffe (z. B. kid: "../../dev/null", wodurch ein leerer Key geladen wird) oder SQL-Injections.

Best Practice: kid-Werte ausschließlich gegen eine strikt definierte In-Memory-Whitelist (z. B. Dictionary oder Set) abgleichen. Unbekannte oder syntaktisch ungültige IDs müssen sofort zur Ablehnung führen.

Python
import jwt

TRUSTED_KEYS = {
    "key-2026-auth1": PUBLIC_KEY_1,
    "key-2026-auth2": PUBLIC_KEY_2,
}

def verify_token_kid(token: str) -> dict:
    headers = jwt.get_unverified_header(token)
    kid = headers.get("kid")

    # Striktes Mapping gegen Path Traversal und Injections
    key = TRUSTED_KEYS.get(kid)
    if not key:
        raise PermissionError("Invalid or untrusted kid")

    return jwt.decode(
        token,
        key,
        algorithms=["EdDSA"],
        audience="https://api.example.com",
        issuer="https://auth.example.com",
    )

Viele Implementierungen validieren lediglich die Signatur und das Ablaufdatum (exp). Das reicht für robuste Systeme nicht aus. Jedes Token muss auf seinen Kontext geprüft werden:

  • iss (Issuer): Stammt das Token exakt vom erwarteten Identity-Provider?
  • aud (Audience): Ist das Token explizit für dieses Backend bestimmt? Fehlt dieser Check, kann ein Token eines Low-Security-Dienstes an kritischen APIs wiederverwendet werden.
  • nbf (Not Before) & exp (Expiration): Enge Zeitfenster erzwingen.
  • Clock-Skew minimieren: Standardmäßig tolerieren manche Libraries bis zu 5 Minuten Zeitabweichung (Clock Skew/Leeway). Bei einem Token mit 5 Minuten Gültigkeit verdoppelt das die Lebensdauer. Die Toleranz sollte serverseitig auf maximal 15 bis 30 Sekunden gesetzt werden.
Typescript
import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.example.com/.well-known/jwks.json'),
  { cacheMaxAge: 600_000 } // 10 Minuten Caching gegen DoS
);

export async function validateAccessToken(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    algorithms: ['RS256'],
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com',
    clockTolerance: '15s',
  });

  return payload;
}

Die JWT-Spezifikation erlaubt Header-Felder wie jku (JWK Set URL), x5u (X.509 URL) oder direkt eingebettete Schlüsselobjekte (jwk). Wenn Bibliotheken diese URLs unkritisch auflösen, eröffnet das verheerende Server-Side Request Forgery (SSRF)-Angriffsvektoren oder erlaubt es Angreifern, dem Backend eigene Schlüssel unterzuschieben.

Regel: Schalte alle automatischen Fetch-Funktionen für externe Schlüssel im Header ab. Das Backend bestimmt die Quellen für öffentliche Schlüssel statisch oder über hart codierte, intern aufgelöste JWKS-Endpunkte.


Langlebige Access Tokens sind das größte Sicherheitsrisiko im operativen Betrieb. Wird ein Token mit 24 Stunden Laufzeit entwendet, hat der Angreifer einen Tag lang uneingeschränkten Zugriff.

  • Access-Token-Lebensdauer: 5 bis maximal 15 Minuten.
  • Session-Verlängerung: Ausschließlich über kurzlebige Refresh Tokens mit Refresh Token Rotation (RTR).
  • Automatic Family Invalidation: Jeder Refresh-Token gehört zu einer Token-Familie. Wird ein bereits verwendeter Refresh-Token ein zweites Mal eingelöst, deutet das auf Token-Diebstahl hin. Die gesamte Token-Familie muss umgehend invalidiert und die Sitzung beendet werden.

Klassische Bearer-Tokens haben einen fundamentalen Schwachpunkt: Wer das Token besitzt, darf agieren. Gelingt es einem Angreifer, ein Token via XSS, Log-Leakage oder Man-in-the-Middle abzufangen, kann er es sofort missbrauchen.

DPoP (Demonstrating Proof-of-Possession) löst dieses Problem auf Protokollebene. Der Client generiert ein kurzlebiges, asymmetrisches Schlüsselpaar und signiert jeden API-Request mit einem DPoP-Header. Der Server verifiziert, dass das Access Token kryptografisch an den Public Key des Clients gebunden ist. Ein gestohlenes Access Token ist für Angreifer wertlos, da sie den privaten Client-Schlüssel nicht besitzen.


Für gewöhnliche GET-Abfragen genügt die kurze Laufzeit. Bei kritischen Aktionen (z. B. Überweisungen, Passwortänderungen oder Rechte-Eskalationen) muss ein strikter Replay-Schutz greifen.

Hier kommt der Claim jti (JWT ID) zum Einsatz. Bei Ausführung der Operation schreibt das Backend den jti-Hash mit einer Time-to-Live (TTL entsprechend der Restlaufzeit des Tokens) in einen In-Memory-Speicher wie Redis. Geht derselbe jti erneut ein, wird die Anfrage mit 409 Conflict bzw. 403 Forbidden abgewiesen.


Zwei oft übersehene Architekturdetails gefährden die Verfügbarkeit und Integrität:

  1. Krypto-DoS über JWKS: Angreifer können Tokens mit gigantischen RSA-Schlüsseln (z. B. 8192 Bit) oder komplex strukturierten JWKS senden, um die CPU des Verifizierers lahmzulegen. Begrenze die akzeptierten Schlüssellängen, bevorzuge moderne Kurven wie Ed25519 (EdDSA) und cache JWKS-Antworten mit Timeout.
  2. Nested Token Confusion (JWS vs. JWE): Werden signierte (JWS) und verschlüsselte (JWE) Tokens in derselben Infrastruktur verwendet, muss die Reihenfolge eindeutig festgelegt sein. Ein unvollständig validiertes JWE darf niemals ungeprüft als JWS weitergeleitet werden.

  1. Algorithmen festlegen: algorithms=['RS256'] oder ['EdDSA'] hart im Code verankern.
  2. Header isolieren: Keine dynamischen Datei- oder DB-Zugriffe via kid, keine Requests via jku.
  3. Payload bereinigen: Niemals sensible PII oder Passwörter in unverschlüsselte JWS-Payloads legen (OWASP Cheat Sheet Compliance).
  4. Signatur vor Claim-Verarbeitung: Niemals Claims vor erfolgreicher kryptografischer Prüfung auslesen.
  5. Enge Zeittoleranzen: 5–15 Minuten exp, maximal 30 Sekunden Clock Tolerance.

Planst du die Absicherung deiner API-Infrastruktur oder möchtest bestehende JWT- und OAuth2-Workflows auditieren lassen? Kontaktiere mich gerne über Büngener Software für pragmatische und belastbare Sicherheitslösungen im Backend.