As seis razões pelas quais um token válido é recusado
Repare no “válido”: o token está bem formado, você consegue decodificar, e mesmo assim a API devolve 401. Essas são as causas, na ordem em que aparecem na vida real.
1. exp — expirou
A campeã, e a mais fácil de confirmar. exp é um horário em
segundos desde 1970 (não milissegundos — errar isso é
clássico em JavaScript, onde Date.now() devolve
milissegundos). Se o número for treze dígitos em vez de dez, alguém
esqueceu de dividir por mil e o token “expira” daqui a 50 mil anos.
2. nbf — ainda não vale
Not before. O token existe, mas só passa a valer no futuro. Quando isso aparece em produção, quase sempre não é o token: é o relógio da máquina que emitiu estar adiantado em relação ao de quem valida.
3. Relógio fora de sincronia
Este é o que faz gente perder o dia. O token acabou de ser emitido e a API diz que já expirou — porque os dois servidores discordam em alguns segundos. O sintoma denuncia: falha intermitente, mais frequente em tokens de vida curta.
O conserto não é emitir outro token, é aceitar uma folga na validação — e sincronizar o relógio das máquinas:
// jsonwebtoken (Node)
jwt.verify(token, chave, { clockTolerance: 30 }) // segundos
// jose
await jwtVerify(token, chave, { clockTolerance: '30s' })
4. aud ou iss não batem
O token é legítimo, só que não é para você.
aud diz para qual serviço ele foi emitido e iss
diz quem emitiu. Um token do ambiente de homologação chegando na API de
produção falha exatamente assim.
Detalhe que morde: aud pode ser string ou lista.
Validador que compara com === quebra quando vem lista.
5. O algoritmo não é o que a API espera
Aqui mora um problema de segurança, não só de configuração. Se o
verificador aceita o algoritmo que o token declara, um atacante
troca RS256 por HS256 e assina usando a chave
pública — que é pública — como se fosse segredo compartilhado. Pior:
alg: none pede para o verificador não conferir nada.
Por isso todo verificador sério exige que você diga o algoritmo esperado:
jwt.verify(token, chavePublica, { algorithms: ['RS256'] })
Se a sua API recusa com “invalid algorithm”, ela está fazendo o certo — o lado errado é quem emitiu.
6. A chave — kid, rotação e JWKS
Quando as claims passam e a rejeição continua, sobrou a assinatura. O
caminho mais comum não é chave errada, é chave certa que não foi
encontrada: o header traz um kid, o verificador
procura essa chave no JWKS do emissor e não acha, porque houve rotação e o
cache local está velho.
# o que o verificador deveria enxergar
curl -s https://auth.exemplo.com/.well-known/jwks.json | jq '.keys[].kid'
Se o kid do seu token não estiver nessa lista, o problema é
cache ou emissor errado — não o token.
Como saber que a rejeição é de assinatura
Por eliminação, e é por isso que esta página confere as claims uma a uma.
Se exp, nbf, aud, iss e
o algoritmo estão todos certos e a API continua devolvendo 401, restam
três hipóteses, nesta ordem:
Chave errada — o segredo da API não é o mesmo que assinou.
Acontece muito entre ambientes.
Chave não encontrada — o kid não está no JWKS
atual.
Token adulterado — alguém mexeu no payload sem reassinar.
Aí a rejeição é o sistema funcionando.
O que nunca fazer com um JWT
- Não guarde dado sensível no payload. Ele é codificado, não criptografado — qualquer pessoa lê em dois segundos. Assinatura garante que ninguém alterou, não que ninguém leu.
- Não decodifique sem verificar. Ler o payload para decidir permissão, sem conferir a assinatura, é confiar em texto que o cliente pode reescrever.
- Não cole token de produção em site aleatório. Inclusive este: aqui a conta é toda no navegador e dá para conferir no código-fonte da página — mas o hábito certo é usar token de teste.
Se o 401 vier acompanhado de erro no console do navegador em vez da API, talvez nem seja o token — pode ser o navegador barrando a requisição antes. Isso é assunto de CORS, e o artigo de proxy reverso explica de onde os cabeçalhos vêm.