Autenticação10 min de leitura

JWT: decodificar não é validar — o que o backend precisa checar de verdade

Ler o payload de um JWT é Base64, não segurança. Veja a anatomia do token, o ataque do alg none, a lista de claims que precisam ser verificadas e como evitar os erros mais comuns de autenticação.

Publicado em

AL

Por André Leitão

Desenvolvedor de software

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.

Estrutura de um JWT
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.

Claims registradas e o que verificar em cada uma
ClaimSignificadoVerificação no servidor
expExpiraçãoRejeitar se o instante atual for posterior, com tolerância de poucos segundos
nbfNão antes deRejeitar se o instante atual for anterior
iatEmitido emUsar para detectar tokens antigos demais para operações sensíveis
issEmissorComparar com a lista de emissores confiáveis, sem aceitar qualquer valor
audDestinatárioConfirmar que a sua API está na audiência declarada
subSujeitoIdentificador do usuário; nunca confiar em campos paralelos como e-mail
jtiIdentificador do tokenBase 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.

Verificação com algoritmo e audiência fixados no servidor
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

  1. 1Decodificar sem verificar. Chamar decode em vez de verify é o erro mais direto: ele lê o payload e ignora completamente a assinatura.
  2. 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.
  3. 3Ignorar a expiração porque atrapalhava os testes. A configuração temporária vira permanente e o token vira eterno.
  4. 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.
  5. 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.
  6. 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.
  7. 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égias de revogação e seus custos
EstratégiaComo funcionaCusto
Expiração curta com refreshAccess token de minutos, refresh token revogávelMais requisições ao serviço de autenticação
Lista de revogadosConsulta de jti em cache compartilhadoReintroduz estado e uma dependência na validação
Versão de sessãoClaim com a versão, comparada com a do usuárioUma 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.

Perguntas frequentes

Qualquer pessoa consegue ler o conteúdo de um JWT?
Sim. O header e o payload são apenas Base64URL, sem criptografia. Qualquer um que intercepte o token lê tudo que há dentro. A assinatura protege contra alteração, não contra leitura — por isso nunca coloque dado sensível no payload.
Então o que a assinatura garante?
Integridade e origem. Ela prova que o token foi emitido por quem detém a chave e que nenhum byte foi alterado depois. Não prova que o token ainda é válido no tempo, que se destina à sua API nem que o usuário continua com as permissões que estão ali.
O que é o ataque do alg none?
É quando o atacante troca o algoritmo declarado no header por 'none' e remove a assinatura. Bibliotecas antigas ou mal configuradas aceitavam o token como válido. A defesa é nunca deixar o token escolher o algoritmo: fixe no servidor quais algoritmos são aceitos.
Vale a pena guardar JWT no localStorage?
É a opção mais exposta, porque qualquer script injetado na página lê o token. Cookie com HttpOnly, Secure e SameSite tira o token do alcance do JavaScript, ao custo de exigir proteção contra CSRF. A escolha depende da arquitetura, mas localStorage nunca deveria ser o padrão irrefletido.
Como revogar um JWT antes da expiração?
Não dá, por natureza — o token é autocontido e o servidor não consulta nada para aceitá-lo. As saídas práticas são manter expiração curta com refresh token, guardar uma lista de identificadores revogados consultada na validação, ou versionar as sessões e comparar a versão na claim.

Ferramentas relacionadas

AL

Sobre o autor

Desenvolvedor de software no Brasil. Mantém o Codigio Labs desde a primeira ferramenta, escreve os artigos do site e revisa os textos quando a informação técnica muda.

Encontrou um erro ou tem uma correção a sugerir? Fale com o Codigio Labs. Correções relevantes são aplicadas com atualização da data de revisão do texto.

Continue lendo