Fehlermeldung
unexpected "iss" claim value
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED
Was der Fehler bedeutet
Das Token hat die kryptografische Prüfung bestanden, aber sein Claim `iss` ist nicht gleich der Option `issuer`, die du `jwtVerify` übergeben hast. jose vergleicht die Strings exakt (ein Array erlaubter Aussteller wird ebenfalls akzeptiert), deshalb lässt jede Abweichung – ein Schrägstrich am Ende, ein anderer Mandantenpfad, eine andere Umgebung – die Prüfung scheitern. Erst das Pinning des Ausstellers verhindert, dass ein Token eines anderen Identity Providers oder eines anderen Mandanten desselben Providers akzeptiert wird.
Warum der Fehler auftritt
Schrägstrich am Ende oder Schema weicht ab
häufigDie Provider sind uneinheitlich: Auth0 verwendet als Aussteller `https://tenant.auth0.com/` mit Schrägstrich, Keycloak und Entra ID v2.0 (`…/v2.0`) verwenden URLs ohne, während Tokens von Entra ID v1.0 `https://sts.windows.net/<tenant>/` mit Schrägstrich tragen. Für einen exakten Vergleich sind `https://login.example.com` und `https://login.example.com/` verschiedene Aussteller.
Falscher Mandant, Realm oder Autorisierungsserver
häufigMandantenfähige Provider nehmen den Mandanten in den Aussteller auf: Entra ID `https://login.microsoftonline.com/<tenant-id>/v2.0`, Keycloak `https://host/realms/<realm>`, Okta `https://<org>.okta.com/oauth2/<server-id>`. Prüfst du gegen den Standardserver, während die Tokens von einem benutzerdefinierten stammen (oder gegen den falschen der beiden Entra-Endpunkte v1.0 und v2.0), entsteht dieser Fehler.
Staging-Token beim Prüfer in der Produktion
gelegentlichTokens aus einem Staging- oder Entwicklungsmandanten tragen den Aussteller dieses Mandanten. Wird die Konfiguration zwischen Umgebungen kopiert, laufen Prüfer und Token-Quelle auseinander.
Mehrere legitime Aussteller, nur einer konfiguriert
gelegentlichDienste, die Tokens von mehr als einem Identity Provider akzeptieren, konfigurieren manchmal nur den ersten. Die Lösung ist eine bewusste Allowlist mit einem Schlüsselsatz pro Aussteller, nicht das Weglassen der Option `issuer`.
So behebst du den Fehler
- 1.
Aussteller-String aus dem Discovery-Dokument übernehmen
Der Wert `issuer` in `/.well-known/openid-configuration` ist laut Spezifikation genau das, was der Provider in `iss` schreibt. Kopiere ihn Byte für Byte in deine Konfiguration.
bash # The issuer string is defined by the provider's discovery document. curl -s https://login.example.com/.well-known/openid-configuration | jq -r .issuer - 2.
Genau diesen Wert in jwtVerify fest vorgeben
Behalte die Option `issuer` bei – ohne sie prüft jose `iss` überhaupt nicht – und halte den Wert in einer einzigen Konstante, die JWKS-URL und Prüfer gemeinsam nutzen.
js import { createRemoteJWKSet, jwtVerify } from "jose"; const ISSUER = "https://login.example.com/"; // exactly as in "issuer" from discovery const jwks = createRemoteJWKSet(new URL("https://login.example.com/.well-known/jwks.json")); const { payload } = await jwtVerify(token, jwks, { algorithms: ["ES256"], issuer: ISSUER }); - 3.
Mehrere Aussteller mit je einem eigenen Schlüsselsatz akzeptieren
Vertraut dein Dienst tatsächlich mehr als einem Aussteller, wähle den Schlüsselsatz anhand des (noch ungeprüften) `iss` des Tokens aus einer expliziten Allowlist und prüfe dann mit genau diesem fest vorgegebenen Aussteller. Lass niemals die Schlüssel eines Ausstellers die Tokens eines anderen Ausstellers prüfen.
js import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose"; // Each trusted issuer gets its own key set; an unknown iss is rejected before any key is used. const trusted = new Map([ ["https://login.example.com/", createRemoteJWKSet(new URL("https://login.example.com/.well-known/jwks.json"))], ["https://partner.example.org", createRemoteJWKSet(new URL("https://partner.example.org/jwks"))], ]); const { iss } = decodeJwt(token); const keys = trusted.get(iss); if (!keys) throw new Error("untrusted issuer"); await jwtVerify(token, keys, { algorithms: ["ES256", "RS256"], issuer: iss });