Decodificador de Tokens JWT
Decodifica JSON Web Tokens e visualiza seu conteúdo.
Decoding only — the signature is not verified. Nothing is sent anywhere.
Visão geral
Um JWT são três partes codificadas em base64url e separadas por pontos: um cabeçalho que diz como ele foi assinado, um payload de claims e uma assinatura sobre as duas primeiras. A codificação não é criptografia. Quem tiver o token consegue ler todos os claims dentro dele, e vale internalizar isso antes de colocar qualquer coisa em um payload.
Esta ferramenta decodifica as duas primeiras partes e as formata, depois extrai os três claims de tempo e os converte de segundos Unix em datas que você consegue ler. Ela não verifica a assinatura — veja abaixo por que essa é a decisão correta e não uma funcionalidade faltando.
Como usar
- Cole o token. Tudo o que vem depois de
Bearer— o esquema em si não faz parte do JWT. - Leia o cabeçalho para ver o algoritmo e, se estiver presente, o id da chave.
- Leia o payload para ver os claims.
- Confira a tabela de datas para
iat,nbf,expe o veredicto de expiração.
Os claims registrados
| Claim | Nome | Observações |
|---|---|---|
iss | Emissor | Quem emitiu o token. Seu servidor deveria conferir isso contra uma lista de permissões |
sub | Sujeito | Sobre quem o token é. Normalmente um id de usuário estável, não um e-mail |
aud | Audiência | Para quem o token foi emitido. Um token emitido para outra audiência tem de ser rejeitado |
exp | Expiração | Segundos Unix. Depois disso, rejeite |
nbf | Não antes de | Segundos Unix. Antes disso, rejeite |
iat | Emitido em | Segundos Unix. Útil para «reautenticar se for mais antigo que N» |
jti | ID do JWT | Id único, para que um token possa ser revogado ou checado contra replay |
Todos os sete são opcionais na especificação. Um token que omite exp nunca
expira por conta própria, o que é uma decisão de projeto digna de nota quando
você a encontra.
Por que a verificação de assinatura não é oferecida
Verificar uma assinatura exige a chave: o segredo compartilhado no caso do
HS256, ou a chave pública no caso de RS256 e ES256. No caso simétrico, a
chave que verifica é também a chave que assina — então uma página que
oferecesse verificação estaria pedindo que você colasse a credencial que permite
a qualquer um emitir tokens para o seu sistema. Não existe versão disso que seja
segura de construir.
Decodificar continua valendo a pena. A maior parte da depuração de JWT é «o que
este token diz, afinal» — audiência errada, escopo faltando, um exp cinco
minutos no passado, um sub que é um e-mail quando o seu código espera um UUID.
Tudo isso fica visível sem a chave.
O que decodificar não pode dizer é se o token é autêntico. Um JWT que você recebeu é uma alegação, não um fato, até que o seu servidor confira a assinatura contra uma chave em que confia.
O campo alg não é uma decisão que o seu verificador deva delegar
O alg do cabeçalho descreve o que o remetente usou. Um verificador que lê o
alg e escolhe o algoritmo de acordo está confiando em uma entrada controlada
pelo atacante, o que produz duas quebras clássicas:
alg: none. O JWT não protegido é legal na especificação. Um verificador que o honre aceita qualquer payload com assinatura vazia.RS256trocado porHS256. Se uma biblioteca pega a chave pública e a usa como segredo de HMAC, um atacante que conheça a sua chave pública — ela é pública — consegue assinar tokens que você vai aceitar.
Os dois são problemas de configuração, não de criptografia. Fixe o algoritmo esperado no seu verificador e rejeite qualquer outro, em vez de lê-lo do token.
Exemplos
- Um 401 que você não consegue explicar. Decodifique e confira o
aud. Um token emitido para uma API e apresentado a outra é a causa mais comum, e a mensagem de erro raramente diz isso. - Um 401 intermitente. Compare
expcomiat. Um tempo de vida curto mais um cliente que guarda o token em cache é intermitente por construção. - Uma permissão que deveria funcionar. Procure
scopeourolesno payload. Se o claim estiver ausente, o problema está no emissor, não no seu código de autorização. - Auditar o que você coloca em um token. Decodifique um dos seus. Tudo o que estiver ali — endereço de e-mail, ids internos, feature flags — é legível por quem tiver o token, inclusive pelo navegador onde ele está guardado.
Onde um token deve morar em um navegador
Isso aparece toda vez que alguém decodifica um token e nota que ele é legível. A versão curta: como um JWT é uma credencial ao portador, qualquer coisa que consiga lê-lo consegue usá-lo.
O localStorage é legível por qualquer script na origem, o que significa que um
XSS leva o token com ele. Ele também nunca é enviado automaticamente, então você
não pode sofrer CSRF por meio dele — você troca uma classe de bug por outra.
Um cookie HttpOnly não pode ser lido por script nenhum, então um XSS não
consegue roubá-lo. Ele é enviado automaticamente, então precisa de
SameSite=Lax ou Strict mais Secure, e todo endpoint que altera estado
precisa da própria defesa contra CSRF.
A conclusão usual para uma aplicação de navegador é um access token de vida curta
mantido apenas em memória, renovado a partir de um cookie de refresh token
HttpOnly, Secure e com SameSite. Assim um XSS consegue um token com minutos
de vida em vez de uma credencial que dura semanas, e o segredo de vida longa
nunca fica alcançável pelo JavaScript.
Qualquer que seja a sua escolha, é o exp que faz o trabalho de verdade. Um
token com uma hora de vida e sem lista de revogação é válido por uma hora depois
de ser roubado, e nada do lado do cliente muda isso.
Observações
O padding é acrescentado antes da decodificação, porque base64url normalmente o omite. Tokens copiados de logs às vezes chegam com o padding intacto, e as duas formas funcionam aqui.
O veredicto de expiração usa o seu relógio local, então é uma conveniência e não
uma autoridade. Se você precisa comparar com um instante específico, use o
conversor de Unix time no valor bruto de exp.
Payloads não são sempre JSON em princípio — a especificação permite qualquer tipo de conteúdo — mas todo JWT na prática carrega um objeto JSON, e um payload que não consegue ser interpretado como JSON é exibido como texto bruto em vez de escondido.