Decodificando JWT no navegador: por que o segredo não importa aqui
JWT é assinado, não criptografado. Entenda o que dá pra inspecionar sem nenhuma chave, e quando isso é seguro fazer client-side.
Toda semana alguém abre uma issue perguntando se é seguro decodificar tokens JWT no client — e a resposta surpreende quem espera um "depende". Spoiler: pra inspecionar o payload, depende muito pouco.
Este post explica por quê, mostra como fazer no navegador em sete linhas de JavaScript, e enumera onde a coisa muda de figura.
Anatomia de um JWT
Um JWT é só três strings em Base64URL separadas por ponto:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
A primeira é o header (algoritmo, tipo). A segunda é o payload (suas claims). A terceira é a assinatura — calculada a partir das duas primeiras mais um segredo.
Repare: nenhuma das partes é criptografada. Base64URL não é criptografia, é só uma forma de embalar bytes em ASCII seguro pra URL. Qualquer um que tenha o token consegue ler o payload. Qualquer um. Inclusive você, sem precisar de nenhuma chave.
Decode ≠ Verify
Aqui está a confusão que vejo todo dia:
Decodificar um JWT te diz o que ele afirma. Verificar te diz se você pode acreditar nele.
São operações completamente diferentes. Decodificar precisa de zero segredo — qualquer pessoa faz, e não há problema nisso. Verificar precisa da chave compartilhada (HMAC) ou pública (RSA/ECDSA), e idealmente acontece no servidor.
Misturar as duas é a raiz da maioria dos bugs de segurança com JWT. A claim { "sub": "12345", "role": "admin" } não vale nada se você não verificou a assinatura antes de confiar nela.
Fazendo isso no navegador
O decoder JWT do Quick Tools faz literalmente isto:
function decodeJwt(token) {
const [h, p] = token.split('.');
return {
header: JSON.parse(atob(h.replace(/-/g, '+').replace(/_/g, '/'))),
payload: JSON.parse(atob(p.replace(/-/g, '+').replace(/_/g, '/'))),
};
}
Sete linhas, zero dependências, zero rede. O token nunca sai do seu navegador — não precisaria, porque qualquer parte interessada já consegue ler também.
Por que atob precisa do replace?
Base64URL substitui + por - e / por _ pra sobreviver dentro de URLs sem encoding. atob só entende o Base64 canônico (RFC 4648), então a gente desfaz a troca antes de decodificar. É um detalhe que aparece toda vez que alguém tenta implementar isso do zero sem ler a especificação.
O que tem no payload?
Tipicamente: sub (subject — quem é o usuário), iat (issued at), exp (expiration), e as claims customizadas que sua aplicação definiu. Tudo isso é legível sem nenhuma chave. Por isso, nunca coloque informação sensível no payload — dados de cartão, senha temporária, qualquer coisa que não deva ser lida por qualquer intermediário.
O que você NÃO deve fazer
- Confiar no payload sem verificar a assinatura no backend. Qualquer pessoa forja claims; só a assinatura garante autenticidade.
- Guardar segredos HMAC no client. Se a chave está no bundle JavaScript, ela está efetivamente pública.
- Colar tokens de produção em ferramentas online que fazem requests para um servidor. Você não sabe o que elas fazem com o token. Use ferramentas client-only — é exatamente pra isso que elas existem.
- Usar o algoritmo
none. Algumas bibliotecas antigas aceitavam JWTs sem assinatura. Se a sua versão permite isso, atualize agora.
Decodifique com confiança, verifique no backend
Decodificar JWT no navegador é seguro e útil para debug, logging, e mostrar informações ao usuário (como nome de exibição ou permissões). Verificar JWT no navegador quase sempre é o sintoma de uma arquitetura que deveria mover essa lógica para o backend.
A regra é simples: decodificação é presentação, verificação é autorização. Nunca tome decisões de acesso baseadas em um token que só foi decodificado, não verificado.
O que pode e o que não pode ser feito no navegador
Decodificar JWT no navegador é seguro quando o objetivo é inspecionar header e payload. Esses dados já estão no token e não exigem segredo para leitura. Isso é útil para verificar exp, iat, aud, iss, roles e claims customizadas durante debug.
O que não deve acontecer no navegador é validação de confiança. A assinatura precisa ser verificada no backend ou em um ambiente onde a chave pública/segredo e as regras de permissão estejam sob controle. Um usuário pode alterar o payload localmente; sem validação de assinatura, o conteúdo não prova nada.
Checklist de debug
Ao inspecionar um token, confira primeiro o algoritmo, depois emissor, audiência e expiração. Verifique se o horário está em epoch seconds, não milliseconds. Em seguida, confirme se as claims usadas pela interface são apenas informativas. Autorização real deve vir de resposta do servidor.
Use o Decodificador JWT para ler o token sem enviá-lo a terceiros. Mesmo assim, evite colar tokens reais de produção se eles ainda estiverem válidos.
- 01 diff antes de abrir PR: o hábito que reduz comentários de revisão Ler o próprio diff antes de abrir um PR muda o que você vê no código. Como e por que esse hábito reduz ciclos de revisão e melhora a qualidade dos PRs.
- 02 `P@ssw0rd1` passa na sua validação. Esse é o problema. O que o NIST mudou em 2024 sobre senhas: comprimento bate complexidade, leet speak está no rockyou.txt e rotação obrigatória enfraquece senhas.