Desconecte um usuário de uma aplicação baseada em sessão e o servidor o esquece na próxima requisição. Desconecte-o de uma aplicação baseada em JWT e, a menos que você tenha construído um mecanismo extra para isso, o token que ele está carregando continua válido até expirar. Essa diferença é a verdadeira decisão por trás de “JWT vs sessão”. O resto decorre dela.
O que uma sessão realmente guarda
Uma sessão mantém o estado do usuário no servidor e passa ao navegador apenas uma chave de consulta.
- O usuário faz login com uma senha.
- O servidor cria um registro de sessão (ID do usuário, papéis, o que for preciso) e o guarda em memória, em um banco de dados ou no Redis, indexado por um ID de sessão aleatório.
- O servidor envia esse ID ao navegador como cookie, marcado
HttpOnlypara que o JavaScript do lado do cliente não possa lê-lo. - A cada requisição, o navegador devolve o cookie automaticamente. O servidor procura o ID no store e recupera os dados da sessão.
O cookie não carrega nenhuma informação do usuário, só uma string opaca que o servidor resolve a cada requisição. Apague o registro no store e a sessão desaparece na próxima requisição, em todos os dispositivos.
O que tem dentro de um JWT
Um JSON Web Token pula essa consulta colocando o estado direto no token, assinado para que não possa ser adulterado.
Um JWT é formado por três partes codificadas em base64url unidas por pontos: header.payload.signature. O payload é um objeto JSON de claims. A RFC 7519 define uma lista curta de claims registrados:
| Claim | Nome | Significado |
|---|---|---|
iss | Issuer | Quem emitiu o token |
sub | Subject | A quem o token se refere |
aud | Audience | Para quem o token é destinado |
exp | Expiration | Rejeitar o token depois deste momento |
nbf | Not before | Rejeitar o token antes deste momento |
iat | Issued at | Quando o token foi criado |
jti | JWT ID | ID único, útil para revogação e verificações de replay |
O servidor assina o header e o payload com um secret (HMAC) ou uma chave privada (RSA/ECDSA) e envia o resultado ao cliente. Qualquer servidor que tenha a chave correspondente, ou apenas a chave pública na assinatura assimétrica, pode verificar a assinatura e confiar nos claims. Nenhuma ida e volta ao banco de dados, nenhum store de sessão compartilhado. Em um sistema com vários serviços, cada um pode verificar um token por conta própria.
O lado inverso: quem tiver o token pode ler o payload. Base64 é uma codificação, não uma criptografia. Nunca coloque uma senha, um secret ou dados pessoais que você não imprimiria em um log dentro do payload de um JWT.
Como configurar a autenticação por sessão no Express
Uma configuração típica no Node usa express-session como middleware e connect-redis para que as sessões sobrevivam a um reinício do servidor e funcionem em várias instâncias. Se o Redis é novo para você, como funciona o cache Redis cobre o básico para colocá-lo em funcionamento.
| |
| |
req.session.destroy() remove a entrada no Redis. A próxima requisição com esse cookie não encontra nada e é tratada como desconectada. Nenhuma blacklist de tokens, nada para ajustar depois.
Como configurar a autenticação JWT no Node
O mesmo fluxo com jsonwebtoken, emitindo um access token de curta duração e um refresh token separado:
| |
| |
jwt.verify checa a assinatura e os claims exp/nbf em uma única chamada e lança uma exceção se qualquer um dos dois falhar. Essa é toda a verificação de autenticação, sem nenhum store envolvido.
A opção algorithms: ["HS256"] importa. Sem ela, um token assinado com um algoritmo diferente do esperado ainda poderia passar, e é assim que várias bibliotecas JWT acabaram com vulnerabilidades de “alg confusion”. Fixe no código a lista de algoritmos aceitos na verificação e rejeite sempre alg: "none".
O refresh token é o que torna viável um access token de 15 minutos. O cliente o guarda e, quando o access token expira, troca por um novo par. Guarde o jti do refresh token no servidor para poder revogar só aquela cadeia sem tocar nos access tokens já em circulação.
Por que os JWTs são difíceis de revogar
Uma sessão é um registro em um store: apague-o e o usuário é desconectado em todo lugar. Um JWT é um claim autocontido que qualquer verificador aceita até que exp diga o contrário. Fazer logout, banir um usuário ou rebaixar seu papel não muda nada em um token já emitido, porque o servidor que verifica não tem nenhum registro para apagar.
As mitigações mais comuns:
- Vida curta do access token. 15-30 minutos é a faixa mais usada, para que um token roubado ou velho tenha uma janela pequena.
- Rotação do refresh token. Cada refresh emite um novo refresh token e invalida o antigo; reutilizar um refresh token antigo revoga toda a cadeia. O OAuth 2.1 exige isso (ou tokens sender-constrained) para clientes públicos.
- Uma blacklist bem delimitada. Para matar um token específico antes de expirar, indexe a blacklist pelo
jtimais oiss, não pela string crua do token. O OWASP JWT Cheat Sheet explica por que colocar a própria string na blacklist não é confiável. Uma blacklist checada em cada requisição também traz de volta uma consulta ao banco em cada requisição — exatamente o custo que os JWTs deveriam eliminar.
Se sua aplicação acaba fazendo uma consulta a cada verificação de qualquer forma, uma sessão teria sido o design mais simples.
Onde guardar um JWT no navegador
Em uma aplicação de navegador, coloque o token em um cookie HttpOnly e Secure, não no localStorage. Qualquer script rodando na página pode ler o localStorage, então uma única brecha de XSS entrega todos os tokens que estiverem lá. O custo: uma vez que o token viaja em um cookie, o navegador o envia automaticamente, e você volta a se defender de CSRF com SameSite e tokens CSRF, exatamente como nas sessões. A comparação completa está em LocalStorage, SessionStorage ou Cookies: Qual Usar.
Quando usar sessões e quando usar JWTs
| Situação | Use |
|---|---|
| Uma única aplicação renderizada no servidor, um único backend | Sessões: mais simples, revogação imediata |
| Logout instantâneo ou mudanças de permissão instantâneas | Sessões, ou JWTs com uma blacklist por jti |
| Vários serviços de backend que verificam o usuário cada um por conta própria | JWTs: nenhum store de sessão compartilhado para consultar |
| App mobile ou clientes de API de terceiros | JWTs: bearer tokens combinam com uma API HTTP sem estado |
Nenhum dos dois é mais seguro em abstrato. Uma sessão no Redis e um JWT de 15 minutos com refresh tokens rotacionados se sustentam igualmente bem. A diferença é arquitetural: quantos serviços precisam confiar no claim e quão rápido você precisa conseguir retirá-lo.
Se você está construindo uma aplicação com um único backend, escolha sessões por padrão e ganhe a revogação de graça. Mude para JWTs no dia em que um segundo serviço precisar saber quem é o usuário sem chamar de volta o serviço que o autenticou.