Quase toda API com login usa JWT, e quase todo vazamento de autenticação envolve um JWT mal implementado: secret fraco, token no lugar errado do front-end, payload com dado sensível achando que estava criptografado. Este guia explica o que é um JSON Web Token, decodifica a anatomia de um token real, compara HS256 com RS256 e mostra implementação em Node.js, PHP e Python, com os erros de segurança que você precisa evitar.
O que é JWT?
JWT (JSON Web Token) é um padrão aberto (RFC 7519) pra transmitir informações entre sistemas como um objeto JSON compacto e assinado digitalmente. O uso mais comum é autenticação de APIs REST: depois do login, o servidor emite um token contendo a identidade do usuário e uma assinatura; nas requisições seguintes, o cliente envia o token e o servidor confia nos dados sem consultar o banco, porque a assinatura prova que nada foi alterado.
O detalhe que muda tudo: o JWT é assinado, não criptografado. Qualquer pessoa com o token consegue ler o conteúdo. O que ela não consegue é modificá-lo sem invalidar a assinatura.
Como funciona a autenticação com JWT
- O usuário envia login e senha pro endpoint de autenticação
- O servidor valida as credenciais e emite um JWT assinado, com prazo de expiração
- O cliente guarda o token e o envia em cada requisição:
Authorization: Bearer <token> - O servidor valida a assinatura e a expiração, lê a identidade do payload e responde
Como a validação é só matemática (recalcular a assinatura), nenhuma consulta ao banco é necessária. É isso que torna o JWT stateless e barato de escalar: qualquer instância da API valida o token sozinha.

Anatomia do JWT: header, payload e signature
Um JWT são três blocos de Base64URL separados por pontos: xxxxx.yyyyy.zzzzz. Decodificando um token real:
Header: o algoritmo
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 → decodificado:
{ "alg": "HS256", "typ": "JWT" }
Payload: as claims
// eyJzdWIiOiI0MiIsIm5hbWUiOiJBbmEiLCJleHAiOjE3ODE2MjU2MDB9 → decodificado:
{
"sub": "42", // subject: ID do usuário
"name": "Ana",
"iat": 1781539200, // issued at: emitido em
"exp": 1781625600 // expiration: expira em
}
As claims registradas mais usadas: sub (identidade), exp (expiração), iat (emissão), iss (quem emitiu) e aud (pra quem). Você pode adicionar claims próprias (roles, plano, tenant), lembrando que tudo ali é legível por quem tiver o token.
Signature: a prova de integridade
A assinatura é o HMAC (ou assinatura RSA) dos dois primeiros blocos: HMACSHA256(base64url(header) + "." + base64url(payload), secret). Se alguém alterar uma vírgula do payload, a assinatura recalculada não bate e o servidor rejeita o token. Por isso não existe “decodificar” a assinatura: ela é um cálculo de verificação, não um bloco de dados.
HS256 vs RS256: qual algoritmo usar?
| Critério | HS256 (HMAC) | RS256 (RSA) |
|---|---|---|
| Tipo de chave | simétrica (um secret) | assimétrica (par pública/privada) |
| Quem assina | quem tem o secret | só quem tem a chave privada |
| Quem valida | quem tem o secret | qualquer um com a chave pública |
| Cenário ideal | monolito ou serviço único | microsserviços e terceiros validando |
| Risco principal | secret vaza = qualquer um emite token | gestão de chaves mais complexa |
Regra prática: se o mesmo serviço emite e valida, HS256 com secret forte (32+ bytes aleatórios) resolve. Se vários serviços validam tokens emitidos por um servidor de autenticação central, RS256: a chave privada fica só no emissor e os demais validam com a pública, geralmente distribuída via endpoint JWKS.
Access token vs refresh token
JWT expirado não pode ser “renovado”: é substituído. O padrão de mercado usa dois tokens. O access token (vida curta, 15 minutos) viaja em toda requisição. O refresh token (vida longa, 7 a 30 dias) fica guardado com mais cuidado e serve só pra pedir um access token novo quando o atual expira. O ganho: se um access token vazar, ele vale minutos; e o refresh token, que vale dias, transita raramente e pode ser revogado no servidor, porque esse sim fica registrado no banco.
Onde armazenar o JWT no front-end
| Opção | Vulnerável a | Veredito |
|---|---|---|
| localStorage | XSS (qualquer script lê) | evite pra tokens sensíveis |
| sessionStorage | XSS, e morre ao fechar a aba | mesmo problema do localStorage |
| Cookie httpOnly + Secure + SameSite | CSRF (mitigado pelo SameSite) | recomendado |
| Memória (variável JS) | perde no refresh da página | bom pra access token + refresh em cookie |
O cookie httpOnly vence porque o JavaScript da página não consegue lê-lo: um XSS ainda é grave, mas não exfiltra o token. A combinação madura: access token em memória, refresh token em cookie httpOnly.

Como implementar JWT na prática
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
// emitir
const token = jwt.sign({ sub: usuario.id }, process.env.JWT_SECRET, {
expiresIn: '15m', issuer: 'minha-api'
});
// validar (middleware)
function autenticar(req, res, next) {
const token = (req.get('Authorization') || '').replace('Bearer ', '');
try {
req.usuario = jwt.verify(token, process.env.JWT_SECRET, { issuer: 'minha-api' });
next();
} catch (e) {
res.status(401).json({ erro: 'Token inválido ou expirado' });
}
}
PHP (firebase/php-jwt)
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// emitir
$token = JWT::encode([
'sub' => $usuario->id,
'iat' => time(),
'exp' => time() + 900, // 15 minutos
], $_ENV['JWT_SECRET'], 'HS256');
// validar
try {
$claims = JWT::decode($tokenRecebido, new Key($_ENV['JWT_SECRET'], 'HS256'));
} catch (Exception $e) {
http_response_code(401);
exit(json_encode(['erro' => 'Token inválido ou expirado']));
}
Python (PyJWT)
import jwt, time, os
# emitir
token = jwt.encode(
{"sub": str(usuario.id), "exp": time.time() + 900},
os.environ["JWT_SECRET"], algorithm="HS256")
# validar
try:
claims = jwt.decode(token_recebido, os.environ["JWT_SECRET"],
algorithms=["HS256"]) # lista explícita: nunca aceite "none"
except jwt.InvalidTokenError:
abort(401)
Erros de segurança comuns com JWT
- Aceitar
alg: none: bibliotecas antigas aceitavam tokens “assinados” com algoritmo nenhum. Sempre passe a lista explícita de algoritmos aceitos na validação. - Secret fraco: HS256 com secret tipo “minhasenha123” cai em força bruta offline. Use 32+ bytes de um gerador criptográfico.
- Dado sensível no payload: CPF, e-mail, saldo. O payload é público pra quem tem o token: guarde ali identificadores e deixe os dados no banco.
- Não validar
exp,isseaud: assinatura válida não basta; um token emitido pra outra API do mesmo emissor não deveria funcionar na sua. - Confusão de algoritmo: servidor configurado pra aceitar HS256 e RS256 pode ser enganado a validar a chave pública RSA como se fosse secret HMAC. Aceite um algoritmo só.
- Token de vida longa sem refresh: access token de 30 dias que vaza é uma conta comprometida por 30 dias.
Como revogar um JWT (logout e blacklist)
O calcanhar de aquiles do stateless: um token assinado é válido até expirar, e o servidor não tem “sessão” pra apagar. As soluções, em ordem de simplicidade: vida curta + refresh (o logout apaga o refresh token do banco e o access morre sozinho em minutos, suficiente pra maioria das aplicações); denylist (guarda o jti dos tokens revogados num Redis com TTL igual ao tempo restante, e a validação consulta a lista, sacrificando um pouco do stateless); e rotação de secret (invalida todos os tokens de todos os usuários, último recurso pra incidentes).
JWT vs autenticação por sessão
| Critério | JWT | Sessão (cookie + servidor) |
|---|---|---|
| Estado no servidor | nenhum | registro por usuário logado |
| Escala horizontal | trivial | exige session store compartilhado |
| Revogação imediata | difícil (denylist) | trivial (apaga a sessão) |
| Tamanho por requisição | centenas de bytes | dezenas (só o session ID) |
| Melhor pra | APIs, microsserviços, mobile | aplicações web monolíticas |
Perguntas frequentes
O que é JWT e para que serve?
JWT (JSON Web Token) é um padrão pra transmitir dados assinados entre sistemas em formato JSON compacto. Serve principalmente pra autenticação de APIs: o servidor emite o token no login e valida a assinatura nas requisições seguintes, sem precisar consultar o banco a cada chamada.
JWT é criptografado?
Não. O JWT padrão é assinado e codificado em Base64URL, que qualquer pessoa decodifica. A assinatura impede alteração, não leitura. Nunca coloque dados sensíveis no payload; se precisar de confidencialidade, o padrão JWE adiciona criptografia de verdade.
Devo usar HS256 ou RS256?
HS256 quando o mesmo serviço emite e valida o token (chave simétrica, mais simples). RS256 quando vários serviços ou terceiros precisam validar tokens de um emissor central: eles recebem só a chave pública e ninguém além do emissor consegue assinar.
Como invalidar um JWT antes de expirar?
Não dá pra “apagar” um token assinado. As alternativas: usar access tokens de vida curta com refresh token revogável no servidor, ou manter uma denylist (Redis) com os IDs dos tokens revogados, consultada na validação.
JWT é o mesmo que OAuth?
Não. OAuth2 é um protocolo de autorização (define como conceder acesso); JWT é um formato de token. Eles se combinam: muitos fluxos OAuth2 emitem access tokens no formato JWT, mas você pode usar JWT sem OAuth e OAuth com tokens opacos.
Conclusão
JWT resolve com elegância o problema de autenticar requisições sem estado no servidor, e cobra o preço na revogação. A implementação segura cabe numa lista curta: secret forte (ou RS256 com chave privada bem guardada), expiração curta com refresh token, validação explícita de algoritmo e claims, payload sem dados sensíveis e armazenamento em cookie httpOnly no front. Implementado assim, o token que protege suas APIs deixa de ser o elo fraco delas.

