Todos os artigos
129 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Todos

Como funciona JWT: autenticacao, assinatura e seguranca

Como funciona JWT na pratica: as tres partes do token, o que a assinatura garante, HS256 vs RS256, fluxo de login, expiracao e armadilhas de seguranca.

Como funciona JWT: autenticacao, assinatura e seguranca
COVER · Todos

Você abriu o DevTools de uma aplicação em produção, copiou o token que ela manda em cada request e colou num decoder. Três segundos depois está lendo o e-mail do usuário, o id, o role "admin" e a data de expiração, tudo em texto claro. Ninguém te deu permissão, ninguém precisou da chave. E isso é exatamente como JWT foi projetado para funcionar. O problema não é o token estar legível: é a quantidade de gente que acha que ele está criptografado.

Este artigo explica como funciona JWT na prática: as três partes do token, o que a assinatura realmente garante, a diferença entre HS256 e RS256, o fluxo de login, o custo de ser stateless e os erros de segurança que ainda derrubam APIs hoje.

Anatomia: três partes separadas por ponto

Um JWT é uma string com três blocos Base64URL separados por ponto: header.payload.signature. Cada bloco é decodificável por qualquer pessoa, sem chave nenhuma. Base64URL não é criptografia, é codificação.

O header diz qual algoritmo assina o token e o tipo:

{
  "alg": "HS256",
  "typ": "JWT"
}

O payload carrega os claims, que são afirmações sobre o usuário e sobre o próprio token:

{
  "sub": "1234567890",
  "name": "Ana Souza",
  "role": "admin",
  "iat": 1721827200,
  "exp": 1721830800
}

sub é o subject (quem é o usuário), iat é o momento de emissão, exp é quando o token expira. A terceira parte, a signature, é o resultado de assinar base64url(header) + "." + base64url(payload) com uma chave. Se você decodificar um token de uma sessão sua agora, vai ver header e payload em texto puro. Dá para inspecionar um token e ver os claims sem mandar nada pro servidor, o que é ótimo para debug e péssimo se você colocou dado sensível ali achando que estava escondido.

O que a assinatura garante (e o que não garante)

Aqui mora o mal-entendido mais comum. A assinatura garante integridade e autenticidade: se alguém mudar um único caractere do payload, a assinatura não bate mais e o servidor rejeita o token. E garante que quem emitiu tinha a chave. Só isso.

A assinatura não garante confidencialidade. O payload não é secreto. Qualquer um que tenha o token lê tudo. A regra prática: nunca coloque senha, número de cartão, CPF ou qualquer segredo no payload de um JWT. Coloque um id e busque o resto no servidor. Se você precisa mesmo de payload ilegível, aí entra JWE (JSON Web Encryption), que é outro padrão e raramente o que as pessoas querem quando dizem "JWT".

HS256 vs RS256: segredo compartilhado ou par de chaves

O alg do header decide como o token é assinado e verificado, e a escolha tem consequência arquitetural.

HS256 é simétrico: usa um único secret compartilhado tanto para assinar quanto para verificar. Simples, rápido, e funciona bem quando o mesmo serviço emite e valida os tokens. O problema aparece quando você tem vários serviços que precisam validar: todos precisam do secret, e qualquer um deles, se comprometido, pode forjar tokens válidos.

RS256 é assimétrico: usa uma chave privada para assinar e a chave pública correspondente para verificar. O servidor de auth guarda a privada; qualquer microserviço pode validar com a pública sem nunca poder forjar um token. Para sistemas distribuídos, federação ou quando terceiros precisam validar seus tokens, RS256 (ou ES256) é a escolha sensata. Comece com HS256 se for um monolito; vá para RS256 quando a topologia exigir.

O fluxo de autenticação na prática

O ciclo padrão tem quatro passos. O usuário faz login com credenciais. O servidor valida, monta o payload com os claims e a expiração, assina e devolve o token. O cliente guarda esse token e, em cada request subsequente, manda no header:

GET /api/orders HTTP/1.1
Host: api.exemplo.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

O servidor recebe, verifica a assinatura, confere o exp, lê os claims e decide se autoriza. Repare no que ele não faz: não consulta um banco de sessões. Toda a informação necessária está dentro do token. Esse é o ponto inteiro de JWT e também a origem dos seus problemas. Se você está projetando a API que recebe esse Bearer, vale revisar os princípios de design de uma REST API para não colar autenticação stateless num desenho de rotas inconsistente.

Stateless tem um custo: revogação

Como o servidor não guarda estado de sessão, ele não tem um botão de "deslogar agora". Um token válido é válido até expirar, ponto. Usuário teve a conta comprometida e você quer matar a sessão na hora? Com JWT puro você não consegue, porque o servidor nem sabe que aquele token existe.

As saídas todas reintroduzem algum estado: manter uma blocklist de tokens revogados (e aí você voltou a consultar um store em cada request), usar expiração curta para limitar a janela, ou versionar a credencial e checar uma flag. Nenhuma é grátis. Quem promete "auth sem estado" geralmente não chegou no requisito de revogação ainda.

Expiração e refresh tokens

A defesa prática contra um token vazado é o exp curto. Um access token de 5 a 15 minutos limita o estrago. Mas ninguém quer logar de novo a cada 15 minutos, e aí entra o refresh token: um segundo token, de vida longa, guardado com mais cuidado, cuja única função é pedir novos access tokens.

O par funciona assim: access token curto vai em cada request; quando expira, o cliente usa o refresh token para obter um novo access token sem reautenticar. O refresh token, por ser longo, é o alvo valioso, e por isso ele merece o armazenamento mais seguro que você tiver e idealmente rotação a cada uso (one-time use, detectando reuso como sinal de roubo).

Onde guardar o token (opinião)

Existem duas opções práticas no browser e elas não são equivalentes.

localStorage é conveniente e é a escolha errada para apps sensíveis. Qualquer XSS no seu site lê o localStorage inteiro e exfiltra o token. Uma única dependência comprometida no seu bundle e seus tokens vazam.

Cookie httpOnly + Secure + SameSite não é acessível por JavaScript, então um XSS não consegue ler o token diretamente. O custo é ter que lidar com CSRF (mitigado por SameSite=Strict/Lax e tokens anti-CSRF quando preciso). Para qualquer coisa que mexa com dado de usuário de verdade, prefira cookie httpOnly. localStorage só para tokens de baixo valor onde o XSS já te derrubaria de qualquer jeito.

As armadilhas clássicas de segurança

Duas vulnerabilidades vivem até hoje porque vieram do próprio design.

alg: none. A spec previu um token "sem assinatura" com alg igual a none. Uma biblioteca que respeita esse valor cegamente aceita um token onde o atacante removeu a assinatura e editou o payload à vontade. Mitigação: sua verificação precisa exigir explicitamente o algoritmo esperado e rejeitar none.

Algorithm confusion (RS256 -> HS256). Se o servidor usa RS256 mas a biblioteca decide o algoritmo pelo header do token, um atacante troca o alg para HS256 e assina usando a chave pública (que é, afinal, pública) como se fosse o secret HMAC. Bibliotecas mal configuradas verificam com sucesso. Mitigação, de novo: fixe o algoritmo aceito no servidor; nunca confie no alg que veio dentro do token.

A lição dos dois casos é a mesma: nunca deixe o token escolher como ele próprio será verificado.

Perguntas frequentes

JWT é criptografado?

Não por padrão. Um JWT comum (JWS) é apenas assinado e codificado em Base64URL, o que significa que qualquer pessoa com o token lê o payload inteiro. A assinatura protege contra adulteração, não contra leitura. Se você precisa de payload ilegível, o padrão é JWE, que é separado e bem menos comum.

Posso revogar um JWT antes de expirar?

Não com JWT puro stateless. O servidor não mantém registro do token, então não tem como invalidá-lo sob demanda. As soluções (blocklist, expiração curta, versionamento de credencial) sempre reintroduzem algum estado no servidor. Por isso access tokens de vida curta combinados com refresh tokens são a abordagem padrão.

Qual a diferença entre HS256 e RS256?

HS256 é simétrico: o mesmo secret assina e verifica, ideal para um único serviço. RS256 é assimétrico: a chave privada assina e a pública verifica, permitindo que múltiplos serviços validem sem poder forjar tokens. Para sistemas distribuídos, RS256 (ou ES256) é a escolha mais segura.

Devo guardar o JWT no localStorage?

Para apps sensíveis, não. localStorage é legível por qualquer JavaScript, então um XSS rouba o token na hora. Prefira um cookie httpOnly, Secure e SameSite, que o JavaScript não consegue ler, e trate o risco de CSRF separadamente.

Para levar

JWT é um envelope assinado e legível, não um cofre. A assinatura prova de quem veio e que não foi adulterado; ela não esconde nada. Trate o payload como público, nunca o use no localStorage para dados sensíveis, prenda o algoritmo de verificação no servidor para fechar alg: none e algorithm confusion, e aceite que stateless significa abrir mão da revogação instantânea, compensada com exp curto e refresh tokens. Decodifique seus próprios tokens de vez em quando: o que você vê ali, todo mundo também vê.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil