Mensagem de erro
unexpected "iss" claim value
jose (JavaScript) — JWTClaimValidationFailed, code ERR_JWT_CLAIM_VALIDATION_FAILED
O que significa
O token foi verificado criptograficamente, mas a claim `iss` dele não é igual à opção `issuer` que você passou para `jwtVerify`. O jose compara as strings de forma exata (um array de emissores permitidos também é aceito), então qualquer diferença — uma barra final, outro caminho de locatário, outro ambiente — reprova a verificação. Fixar o emissor é o que impede que um token de outro provedor de identidade, ou de outro locatário do mesmo provedor, seja aceito.
Por que acontece
Diferença na barra final ou no esquema
comumOs provedores não são consistentes: o Auth0 emite `https://tenant.auth0.com/` com barra, o Keycloak e o Entra ID v2.0 (`…/v2.0`) emitem URLs sem barra, enquanto os tokens v1.0 do Entra ID trazem `https://sts.windows.net/<tenant>/` com barra. Para uma comparação exata, `https://login.example.com` e `https://login.example.com/` são emissores diferentes.
Locatário, realm ou servidor de autorização errado
comumProvedores multilocatários incluem o locatário no emissor: Entra ID `https://login.microsoftonline.com/<tenant-id>/v2.0`, Keycloak `https://host/realms/<realm>`, Okta `https://<org>.okta.com/oauth2/<server-id>`. Verificar com o servidor padrão enquanto os tokens vêm de um personalizado (ou confundir os endpoints v1.0 e v2.0 do Entra) gera este erro.
Token de homologação em um verificador de produção
ocasionalTokens de um locatário de homologação ou de desenvolvimento trazem o emissor desse locatário. Quando a configuração é copiada entre ambientes, o verificador e a origem dos tokens acabam divergindo.
Vários emissores legítimos, só um configurado
ocasionalServiços que aceitam tokens de mais de um provedor de identidade às vezes configuram apenas o primeiro. A correção é uma lista deliberada de emissores permitidos, com um conjunto de chaves por emissor — e não remover a opção `issuer`.
Como corrigir
- 1.
Copiar a string do emissor do documento de descoberta
O valor de `issuer` em `/.well-known/openid-configuration` é, por especificação, exatamente o que o provedor grava em `iss`. Copie-o byte a byte para sua configuração.
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.
Fixar esse valor exato no jwtVerify
Mantenha a opção `issuer` — sem ela, o jose não verifica o `iss` de forma alguma — e guarde o valor em uma única constante, compartilhada pela URL do JWKS e pelo verificador.
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.
Aceitar vários emissores com um conjunto de chaves para cada um
Se seu serviço realmente confia em mais de um emissor, selecione o conjunto de chaves pelo `iss` (ainda não verificado) do token, a partir de uma lista explícita de emissores permitidos, e depois verifique com esse emissor fixado. Nunca deixe que as chaves de um emissor verifiquem os tokens de outro emissor.
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 });