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

JWT vs sessão: qual modelo de autenticação usar

Sessão dá controle total e revogação instantânea; JWT escala sem store central, mas revogar é o ponto fraco. Compare os trade-offs e escolha o modelo certo.

JWT vs sessão: qual modelo de autenticação usar
COVER · Todos

Toda vez que um app precisa lembrar quem está logado, alguém abre a discussão: "usa JWT ou sessão?". A pergunta vira religião rápido — tem quem ache sessão coisa de dinossauro e quem trate JWT como bala de prata. Os dois estão errados. São modelos diferentes, com trade-offs concretos, e a escolha errada custa caro: ou você reinventa sessão de um jeito pior, ou cria um sistema que não consegue deslogar ninguém. Este texto compara os dois pelo que importa de verdade — revogação, escala, onde guardar o token e segurança — e termina com uma recomendação direta.

O foco aqui é autenticação de aplicações web e APIs. Não vou cobrir OAuth, OIDC nem fluxo de terceiros; isso é assunto separado.

O modelo de sessão: estado no servidor

Sessão é o modelo clássico. No login, o servidor gera um session id aleatório, guarda os dados associados (quem é o usuário, permissões, o que for) em um store — memória, Redis, banco — e devolve só o id para o cliente, normalmente dentro de um cookie. Em cada request seguinte, o navegador manda o cookie automaticamente, o servidor pega o id, busca os dados no store e sabe quem está falando.

O ponto forte é o controle total. Quer deslogar um usuário agora? Apaga a linha no store. Quer derrubar todas as sessões de alguém depois de uma troca de senha? Apaga todas as entradas daquele usuário. Quer mudar permissões e que valham no próximo request? É só editar o dado no servidor. Nada disso depende de o cliente cooperar. O cookie carrega um id opaco e sem valor por si só — vazou, você revoga.

O preço aparece quando você escala horizontalmente. Se a sessão vive na memória de um processo, o request do usuário tem que cair sempre no mesmo servidor (sticky sessions) ou ele perde o login ao trocar de instância. A solução padrão é um store compartilhado — Redis na frente de todos os servidores. Funciona muito bem, é barato e previsível, mas é mais uma peça de infraestrutura para manter, monitorar e que vira ponto único de falha se você não cuidar.

O modelo JWT: stateless de verdade

JWT (JSON Web Token) inverte a lógica. Em vez de guardar o estado no servidor, ele coloca os dados — os claims — dentro do próprio token, que vai assinado. Um JWT tem três partes separadas por ponto: header, payload e signature.

header.payload.signature
eyJhbGciOiJI...  .  eyJzdWIiOiIxMjM0...  .  SflKxwRJSMeKKF2QT4...

O payload guarda coisas como sub (o usuário), exp (quando expira) e o que você quiser. A assinatura é gerada com uma chave secreta (ou par de chaves) e garante que ninguém alterou o conteúdo. No request seguinte, o servidor só precisa verificar a assinatura com a chave — sem ir a lugar nenhum buscar nada. É aí que está o ganho: não existe store central, não existe lookup. Qualquer servidor que tenha a chave consegue validar o token sozinho.

Isso brilha em dois cenários. APIs sem estado, onde você não quer carregar infraestrutura de sessão, e comunicação entre serviços (service-to-service), onde um microserviço passa o token adiante e cada serviço valida por conta própria. Escala sem dor de cabeça de store compartilhado.

Vale notar que o payload é só base64, não é criptografia — qualquer um lê o conteúdo. Você pode decodificar um token para ver os claims sem mandar nada a servidor nenhum, justamente porque os dados estão ali em texto, só assinados. Não coloque segredo dentro de um JWT.

Revogação: o calcanhar do JWT

Aqui mora o problema honesto do JWT, e é o que mais gente ignora na pressa de adotar. Como o servidor valida o token só pela assinatura, sem consultar nada, ele não tem como saber que aquele token "não vale mais". Trocou a senha? O token antigo continua válido até o exp. Demitiu o funcionário? O token dele funciona até expirar. Detectou que vazou? Não dá para invalidar antes da hora.

A saída usual é manter uma blocklist — uma lista de tokens revogados que o servidor consulta a cada request. Funciona, mas perceba a ironia: você acabou de adicionar um lookup em store central a cada request. Ou seja, matou o "stateless" que era o argumento inteiro a favor do JWT. Você reconstruiu a sessão, só que com mais bytes trafegando e mais complexidade.

A mitigação real é usar exp curto. Um access token que vive 5 a 15 minutos limita a janela de estrago: mesmo sem revogar, o dano expira sozinho rápido. Mas tokens curtos exigem renovação frequente, e é aí que entram os refresh tokens.

Refresh tokens, tamanho e onde guardar

O padrão maduro com JWT é dois tokens. Um access token curto (minutos) que vai em toda request, e um refresh token longo (dias) que serve só para obter um novo access token quando ele expira. O refresh token você guarda no servidor — sim, com estado — e pode revogar. Repare que você voltou a ter um store; a diferença é que ele só é consultado na renovação, não em todo request. É um meio-termo razoável.

Onde guardar do lado do cliente é onde mais gente erra. A resposta certa é um cookie httpOnly, Secure e SameSite. httpOnly impede que JavaScript leia o token, o que corta o roubo via XSS. O erro clássico é jogar o JWT em localStorage "porque é prático": qualquer script injetado lê o token e ele vaza. Cookie httpOnly vale tanto para session id quanto para JWT — não é vantagem de nenhum dos dois, é higiene básica.

Tem também o tamanho. Um session id são poucos bytes. Um JWT com vários claims pode passar de 1 KB, e ele viaja em toda request. Em alto volume, isso soma banda e latência. Não é decisivo na maioria dos casos, mas é real.

A recomendação direta

Para um app web monolítico, comece com sessão e cookie httpOnly. É mais simples, é mais seguro do que a maioria imagina e te dá revogação de graça. Você não precisa de refresh token, não precisa de blocklist, não precisa decidir tempo de expiração com medo. Adicionar Redis quando escalar é trivial e resolvido há décadas.

Não comece com JWT por moda. JWT é a escolha certa quando você tem APIs sem estado de verdade, comunicação entre múltiplos serviços, ou um cenário onde o store central de sessão é o gargalo que você está tentando eliminar. Nesses casos ele é excelente. Fora deles, você normalmente está pagando a complexidade do stateless sem colher o benefício — e o pior contra-senso do mercado é usar JWT em localStorage num app web comum e, na prática, reimplementar sessão de um jeito pior e menos seguro.

Se quiser entender a mecânica de assinatura, claims e verificação por dentro antes de decidir, veja como funciona um JWT por dentro.

Perguntas frequentes

JWT é mais seguro que sessão?

Não, não por natureza. Segurança vem de onde você guarda o token (cookie httpOnly em ambos), do uso de HTTPS e de boa gestão de expiração e revogação. Sessão até leva vantagem em um ponto: revogar é instantâneo. JWT mal usado, em localStorage e com exp longo, é menos seguro que uma sessão bem feita.

Posso revogar um JWT antes de ele expirar?

Não sem abrir mão do stateless. A única forma é manter uma blocklist no servidor e consultá-la a cada request, o que recria a dependência de store central que o JWT prometia evitar. A prática comum é usar exp curto (5 a 15 minutos) com refresh token, aceitando uma janela pequena em que o token continua válido.

Onde devo guardar o token no navegador?

Em um cookie com as flags httpOnly, Secure e SameSite. Isso vale tanto para session id quanto para JWT. localStorage é uma má ideia porque qualquer JavaScript da página — inclusive um injetado via XSS — consegue ler o conteúdo e roubar o token.

Quando JWT realmente compensa?

Em APIs stateless, em arquitetura de microserviços onde o token é repassado entre serviços, e quando o store central de sessão virou gargalo de escala. Nesses contextos o ganho de não ter lookup a cada request é real. Para um app web monolítico, sessão costuma ser a escolha mais simples e segura.

O que levar daqui

Sessão e JWT não competem pelo mesmo lugar. Sessão é estado no servidor: controle total, revogação instantânea, custo de um store compartilhado ao escalar. JWT é stateless: escala sem store central e é ótimo entre serviços, ao preço de revogação difícil e tokens maiores. Escolha pelo problema, não pela moda — monolito web pede sessão, API distribuída pede JWT — e, em qualquer dos dois, guarde o token num cookie httpOnly.

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