Existe uma cena que se repete em revisão de código: o desenvolvedor cola um token no decodificador, vê o payload com o e-mail e as permissões do usuário, e conclui que a autenticação está funcionando. O token abriu, os dados estão lá, o teste passou.
O problema é que abrir um JWT não requer nenhuma chave. É Base64. Um atacante pode montar um token com qualquer conteúdo, e ele vai abrir igualmente bem em qualquer decodificador — inclusive no seu. A diferença entre um token legítimo e um forjado está inteiramente na verificação da assinatura, que acontece depois e em outro lugar.
Anatomia do token
Um JWT é composto por três blocos separados por ponto: header, payload e assinatura. Os dois primeiros são JSON codificados em Base64URL — uma variante do Base64 que troca os caracteres problemáticos em URL e dispensa o preenchimento com sinal de igual.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- header .eyJzdWIiOiIxMjMiLCJuYW1lIjoiTWFyaWEifQ <- payload .dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk <- assinatura
O header declara o algoritmo e o tipo. O payload carrega as claims — as afirmações sobre o usuário e sobre o próprio token. A assinatura é o resultado de aplicar o algoritmo declarado sobre os dois primeiros blocos, usando uma chave que só o emissor conhece.
As claims que importam
O padrão define um conjunto de claims registradas. Elas são opcionais na especificação e obrigatórias na prática — cada uma fecha uma classe de ataque.
| Claim | Significado | Verificação no servidor |
|---|---|---|
| exp | Expiração | Rejeitar se o instante atual for posterior, com tolerância de poucos segundos |
| nbf | Não antes de | Rejeitar se o instante atual for anterior |
| iat | Emitido em | Usar para detectar tokens antigos demais para operações sensíveis |
| iss | Emissor | Comparar com a lista de emissores confiáveis, sem aceitar qualquer valor |
| aud | Destinatário | Confirmar que a sua API está na audiência declarada |
| sub | Sujeito | Identificador do usuário; nunca confiar em campos paralelos como e-mail |
| jti | Identificador do token | Base para revogação e para impedir reuso em fluxos de uso único |
A verificação de audiência é a mais negligenciada e uma das mais perigosas. Sem ela, um token emitido legitimamente para o serviço A é aceito pelo serviço B — o que transforma qualquer serviço comprometido em porta de entrada para todos os outros que compartilham o emissor.
O ataque do algoritmo
O header do token diz qual algoritmo foi usado. A pergunta óbvia é: quem garante que o header não mente? A resposta correta é que o servidor não deve nem perguntar.
Variante 1 — alg none
O atacante substitui o algoritmo por 'none', apaga a assinatura e mantém o ponto final. Bibliotecas que respeitam cegamente o header interpretam isso como um token sem assinatura e o aceitam. O payload pode dizer o que o atacante quiser.
Variante 2 — confusão de algoritmo
Quando o serviço usa RS256, a chave pública é, por definição, pública. O atacante troca o algoritmo para HS256 e assina o token usando essa chave pública como se fosse o segredo compartilhado. Uma implementação que escolhe o modo de verificação a partir do header valida com sucesso.
import jwt from "jsonwebtoken";
export function verificarToken(token) {
return jwt.verify(token, chavePublica, {
// Fixado no servidor: o header do token não escolhe nada.
algorithms: ["RS256"],
issuer: "https://auth.exemplo.com.br",
audience: "api-pedidos",
clockTolerance: 5
});
}Erros comuns em implementações reais
- 1Decodificar sem verificar. Chamar decode em vez de verify é o erro mais direto: ele lê o payload e ignora completamente a assinatura.
- 2Verificar no gateway e confiar no serviço. Se o serviço interno aceita qualquer token que chegue com o cabeçalho certo, qualquer acesso à rede interna vira acesso autenticado.
- 3Ignorar a expiração porque atrapalhava os testes. A configuração temporária vira permanente e o token vira eterno.
- 4Colocar permissões no token e nunca revalidar. Uma permissão revogada continua valendo até o token expirar; com expiração longa, isso é meia hora ou meio dia de acesso indevido.
- 5Guardar dado sensível no payload. CPF, endereço, telefone e cargo ficam legíveis para qualquer um que veja o token em um log, num proxy ou no histórico do navegador.
- 6Segredo fraco em HS256. Um segredo curto é quebrável por força bruta offline, e o atacante nem precisa de acesso ao servidor para tentar.
- 7Token na query string. URLs vão para logs de servidor, histórico de navegador e cabeçalho Referer. O lugar do token é o cabeçalho Authorization.
Expiração e revogação
O JWT é autocontido: o servidor valida sem consultar nada. Essa é a vantagem que o torna atraente em arquitetura distribuída e é exatamente a razão de a revogação ser difícil. Uma vez emitido, o token vale até expirar.
| Estratégia | Como funciona | Custo |
|---|---|---|
| Expiração curta com refresh | Access token de minutos, refresh token revogável | Mais requisições ao serviço de autenticação |
| Lista de revogados | Consulta de jti em cache compartilhado | Reintroduz estado e uma dependência na validação |
| Versão de sessão | Claim com a versão, comparada com a do usuário | Uma leitura por requisição, mas barata e simples |
A combinação mais comum e mais defensável é expiração curta com refresh token: o access token vive de cinco a quinze minutos e não precisa de revogação, enquanto o refresh token é armazenado, rastreável e pode ser invalidado imediatamente no logout ou na troca de senha.
Onde o decodificador ajuda
Nada disso significa que ler o token seja inútil. Durante o desenvolvimento, abrir o payload responde rápido a perguntas concretas: o exp está no passado? A audiência bate com o nome da API? A claim de permissão veio com o nome que o código espera? O relógio do emissor está adiantado?
O decodificador JWT do Codigio Labs faz esse trabalho no navegador — o token não é enviado a nenhum servidor, o que importa quando se está depurando um ambiente que não é o seu. O que ele mostra é o conteúdo; a validação de assinatura, emissor e audiência continua sendo responsabilidade do backend, com a chave correta.
Resumo
- Header e payload são Base64URL: qualquer um lê, ninguém precisa de chave.
- A assinatura garante integridade e origem, não validade temporal nem autorização atual.
- Fixe algoritmo, emissor e audiência no servidor; nunca deixe o token escolher.
- Verifique exp, nbf, iss e aud em toda requisição, inclusive entre serviços internos.
- Não coloque dado sensível no payload e prefira expiração curta com refresh token revogável.