O mal-entendido mais comum que ouco sobre JWT e: este token esta criptografado com JWT.

Nao esta criptografado. O header e o payload de um JWT sao codificados em Base64URL, a mesma familia do Base64. Qualquer pessoa com a string pode cola-la em qualquer decodificador JWT online e ler o identificador do usuario, o prazo de expiracao e a lista de permissoes na hora. Este artigo existe para que da proxima vez que voce ler criptografado com JWT voce possa dizer na hora: isto e assinado, nao criptografado.

Resumo em 30 segundos

  • Um JWT (JSON Web Token) normalmente tem tres partes: header.payload.signature, separadas por pontos.
  • O header e o payload sao codificados em Base64URL (RFC 4648). Sao reversiveis e totalmente legiveis.
  • A signature e uma assinatura digital que o servidor gera sobre as duas primeiras partes usando um segredo ou uma chave privada. Prova que o token nao foi alterado. Nao prova que o conteudo e confidencial.
  • Nunca coloque senhas, API keys ou outros segredos no payload. Para proteger um segredo use criptografia de verdade (AES, libsodium, age, GPG).

Como um JWT real se parece

A ferramenta Decodificador JWT tem um botao Load sample que produz um JWT padrao de tres partes. A ferramenta mostra cada uma das tres partes decodificadas a direita, mais uma nota de seguranca: signature not verified. Essa nota e o estado normal, porque a decodificacao por si so nunca verifica a assinatura.

Em concreto, o header decodificado contem os campos alg e typ. alg e o algoritmo de assinatura, typ normalmente e JWT. O payload decodificado contem claims como iss, sub, aud, iat, exp, jti e role, que representam respectivamente o emissor, o sujeito, a audiencia, a data de emissao, a data de expiracao, o id do token e o papel.

5 cenarios reais onde JWT e usado errado

Cenario 1: colocar a senha do usuario no payload

Exemplo de payload: um campo como password: Hunter2 dentro do payload do JWT.

Por que as pessoas caem: assumem que um token assinado pelo servidor e automaticamente seguro. Na pratica, qualquer um que pegar o token (extensao do navegador, log do cliente, header Referer, cache da CDN) pode cola-lo no jwt.io ou no nosso Decodificador JWT e ler o campo password em um segundo. A assinatura impede adulteracao, nao bisbilhotagem.

Solucao: senhas nunca vao em um JWT. Campos sensiveis vao em um canal criptografado separado (criptografia no servidor, um vault, etc.) e apenas ids e papeis nao sensiveis vao nos claims.

Cenario 2: colocar uma API key ou string de conexao no payload

Exemplo de payload: um campo como api_key: … dentro dos claims.

Colocar uma API key no payload e a mesma classe de erro que colocar uma senha. Um atacante que roubar o JWT de qualquer usuario pode ler a API key do backend direto do payload e chamar qualquer endpoint interno sem passar pela autenticacao.

Solucao: API keys, strings de conexao e tokens sao segredos e devem viver no servidor. Se o cliente precisar referenciar um, passe por um endpoint de autenticacao dedicado que emita um bearer token de curta duracao.

Cenario 3: colocar campos privados do usuario no payload

Exemplo de payload: campos como email, phone e ssn_last4 enfiados nos claims.

JWT existe para passar claims entre servicos, e o caminho intermediario (gateways, logs, CDNs) frequentemente ve o token inteiro. Escrever campos privados no payload equivale a transmitir esses campos por todos os seus sistemas e viola a LGPD e leis de privacidade semelhantes.

Solucao: mantenha o JWT ao minimo de identidade necessaria: sub, role, exp, iss, aud. Deixe o receptor buscar os campos privados no proprio banco de dados usando sub.

Cenario 4: definir alg como none ou nao validar o algoritmo

Exemplo de header: alg: none, ou uma signature vazia.

Este e o ataque de livro texto. O atacante muda alg para none, limpa a signature e forja um payload dizendo admin. Se o servidor nao aplicar o algoritmo de forma estrita, a solicitacao passa direto. Nosso Decodificador JWT mostra um aviso em vermelho quando alg e none ou a signature esta vazia: sistemas de producao normalmente devem rejeitar isso.

Solucao: o servidor deve usar uma lista branca estrita de algoritmos (por exemplo, apenas RS256 ou HS256) e comparar o campo alg contra essa lista. Nunca escolha o algoritmo de verificacao dinamicamente com base no header. Esse e um classico padrao de CVE.

Cenario 5: assumir que exp ainda vale porque o token decodifica

Exemplo de payload: um campo como exp: 1700000000 (segundos) que a biblioteca trata como milissegundos, ou um relogio do servidor que se desviou.

Se o relogio do servidor esta fora de sincronia, o relogio do cliente se desviou, ou a unidade de iat/exp esta errada (milissegundos contra segundos), voce ve o oposto do que espera: um token cujo exp ainda nao chegou e rejeitado, ou um cujo exp ja passou e aceito.

Solucao: o servidor sempre usa o proprio tempo, nunca confia em headers do cliente. Se a ferramenta mostrar iat muito no futuro, ou exp parecendo um valor de 13 digitos de milissegundos em vez de 10 digitos de segundos, ela mostra um aviso millisecondTimestamp. Por isso passar um token pelo Decodificador JWT antes de mergulhar em problemas de auth te economiza horas.

4 fatos contraintuitivos

Fato 1: uma string JWT e 30-50% maior que o JSON original

A codificacao Base64URL infla o tamanho. Tres bytes viram quatro caracteres, e quanto maior o payload maior o overhead. Para claims curtos e invisivel, mas alguns KB de payload mostram a diferenca.

Fato 2: a signature nao te diz que o conteudo e genuino

A assinatura prova uma coisa: o servidor assinou com o segredo, entao mudar um unico byte quebra a assinatura. Nao prova quem assinou, se o assinante e confiavel, ou se a chave de assinatura foi vazada.

A verificacao e trabalho do servidor, nao do cliente. Depois que o cliente recebe um token, tudo que ele pode fazer e envia-lo de volta ao servidor para verificacao. E exatamente por isso que nossa ferramenta diz explicitamente que a signature nao foi verificada.

Fato 3: bibliotecas em linguagens diferentes se comportam diferente por padrao

  • jsonwebtoken (Node.js): aceita muitos algoritmos por padrao. Em producao voce precisa passar uma lista branca explicita como algorithms: [RS256].
  • PyJWT (Python): estrito por padrao. So valida quando voce passa um argumento algorithms= explicito.
  • java-jwt (Java): permissivo por padrao. Voce precisa especificar algorithms manualmente.

Quando voce cruza fronteiras entre linguagens, uma olhada rapida no comportamento padrao da outra biblioteca evita a maioria dos CVEs.

Fato 4: JWT expirado = rejeitado pelo servidor, nao pelo cliente

O claim exp e para o servidor. Mesmo que o cliente veja um token cujo exp ainda esta no futuro, o servidor pode rejeitar mesmo assim. O servidor pode impor um tempo de vida menor, forcar um refresh ou invalidar o token ativamente por outras politicas (mudanca de IP, mudanca de papel, revogacao manual).

Por isso um status que parece valido no Decodificador JWT nao garante que o servidor aceite o token.

Praticas recomendadas

  1. Mantenha o payload ao minimo: sub, iss, aud, exp, iat, jti, role. Sem senhas, API keys, dados privados ou strings de conexao.
  2. Antes de verificar, fixe uma lista branca de algoritmos (por exemplo algorithms: [RS256]) e nunca escolha dinamicamente a partir do header.
  3. Deixe o servidor gerar exp por conta propria, nunca confie no campo exp do cliente. Um cliente que muta o payload e re-assina nao consegue (a assinatura nao vai bater), mas nao dependa disso.
  4. Ao depurar problemas de auth, use o Decodificador JWT offline para ler os claims. Nunca cole um token real em um site que faz upload. Nossa ferramenta roda 100% local: nada sai do navegador e nenhum log e guardado.
  5. Para proteger um segredo, use criptografia de verdade. JWT nao resolve confidencialidade, apenas autenticacao e resistencia a adulteracao. Para segredos use AES-GCM, libsodium ou age.
  6. JWT nao e uma sessao. E sem estado, mas isso nao significa que e revogavel. Para invalidar tokens, o servidor precisa manter uma denylist, ou usar vidas curtas com refresh tokens.

JWT e assinatura mais codificacao, nao criptografia. Quando voce precisa de confidencialidade, use criptografia. Quando voce precisa de autenticacao, JWT e a ferramenta certa.

Teste o nosso Decodificador JWT — clique em Load sample para gerar um JWT padrao e depois Decode para ver os segmentos header, payload e signature, junto com a nota vermelha signature-not-verified. Para comparar Base64 com criptografia de verdade, leia o nosso artigo Base64 nao e criptografia e a ferramenta Codificador de texto.