JWT: o que é, como funciona e como implementar (guia 2026)

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

  1. O usuário envia login e senha pro endpoint de autenticação
  2. O servidor valida as credenciais e emite um JWT assinado, com prazo de expiração
  3. O cliente guarda o token e o envia em cada requisição: Authorization: Bearer <token>
  4. 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.

Diagrama de sequência da autenticação com JWT
Login emite o token; as requisições seguintes são validadas sem consultar o banco

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.

Chave de autenticação entre navegador e servidor
O token transita entre front e API; onde guardá-lo define a superfície de ataque

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, iss e aud: 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.

Leituras relacionadas

Compartilhe nas mídias: