Toda vez que começo um projeto de API no Laravel, alguém pergunta sobre autenticação. E a discussão sempre vai para o mesmo lugar: JWT ou sessões?
É uma pergunta com resposta errada bastante comum. Não por falta de informação. O ecossistema PHP está cheio de tutoriais, comparativos, benchmarks. O problema é que a maioria trata como questão técnica pura o que na prática é questão de contexto.
Tenho preferência. Mas não é absoluta.
Sessões funcionam muito bem. De verdade.
Aqui vai uma coisa que raramente aparece nos tutoriais: para a maioria das APIs Laravel que encontro na vida real, sessões com cookie funcionam perfeitamente bem. O Sanctum existe exatamente pra isso. Você instala, configura o domínio, adiciona o middleware, e tem autenticação stateful com cookies HttpOnly, proteção CSRF embutida, tudo que você precisa pra uma SPA consumindo a própria API.
O argumento de que sessões não escalam é real, mas limitado. Se você tem um único servidor, sessões em arquivo ou em banco resolvem. Se você tem múltiplos servidores atrás de um load balancer, sessões em Redis resolvem. Se você tem uma arquitetura com dezenas de serviços em containers efêmeros distribuídos em múltiplas regiões… tá, aí sessão começa a doer de verdade. Mas quantas APIs chegam nesse patamar?
Já vi equipe colocar JWT num projeto com dois servidores e dez usuários simultâneos porque “não escala”. Não escala o quê?
Onde JWT faz sentido de verdade
Mobile é o caso mais claro. Num app Android ou iOS, você não tem o contexto de cookie de browser, a gestão de sessão de servidor é complicação desnecessária. JWT funciona melhor aqui porque o token fica no storage do dispositivo, viaja no header da requisição, e o servidor não precisa guardar estado nenhum.
Múltiplos clientes independentes também. Se sua API vai ser consumida por um app mobile, por uma SPA, por um sistema de parceiro e por uma integração de terceiro, JWT dá uma uniformidade que simplifica. Todo mundo usa o mesmo mecanismo, o mesmo fluxo de autenticação, a mesma estrutura de header. Menos decisão contextual, menos confusão pra quem está do outro lado integrando.
APIs públicas. Se você está expondo endpoints pra consumo externo, desenvolvedor de fora, integração com sistema de terceiro, JWT é o padrão que eles esperam encontrar. Chegar num onboarding de API com “manda um cookie” é desconfortável pra quem está construindo a integração do lado de lá.
Aliás, o caso mais sólido pra JWT que já encontrei foi num projeto com autenticação federada. Usuário logava no sistema principal e os tokens eram aceitos em serviços separados sem que esses serviços precisassem consultar o serviço central pra cada requisição. O token carregava as claims necessárias, assinado com chave privada, verificado localmente com chave pública em cada serviço. Funcionou bem e com elegância.
O problema que ninguém menciona quando escolhe JWT
Revogação de token.
Com sessão, se você quer deslogar um usuário agora, você destrói a sessão. Feito. Com JWT, o token vai continuar válido até expirar. Você não tem como revogar um JWT sem guardar estado extra em algum lugar.
Já vi isso aparecer de formas desconfortáveis. Usuário muda a senha depois de suspeitar de acesso indevido. O token antigo ainda funciona por mais vinte minutos. Usuário desativa a conta no admin. Quem está com o token ainda consegue fazer requisições. Numa API de calendário de academia, talvez isso não importe muito. Num sistema com dados financeiros ou de saúde, importa muito.
A solução mais comum é uma blocklist de tokens invalidados. O que é, na prática, trazer estado de volta pro servidor. O que você estava tentando evitar quando escolheu JWT.
Pois é. Nenhuma das duas opções é perfeita. Sessão tem o custo de estado no servidor. JWT stateless tem o problema de revogação. A decisão é qual imperfeição dói menos no seu contexto específico.
Pra projetos onde revogação instantânea é crítica, segurança financeira, área de saúde, autenticação corporativa com risco real de comprometimento de credencial, sessão ou JWT com expiração muito curta e refresh token obrigatório. Não tem outro jeito que funcione bem.
Expiração e refresh token: a parte chata que ninguém gosta de implementar
Se você vai com JWT, vai precisar de refresh token em algum momento. É quase inevitável.
Token de acesso com expiração curta, quinze ou trinta minutos, é a configuração mais segura. Mas significa que em menos de uma hora seu cliente vai receber um 401 e precisar pedir um token novo com o refresh token. Se você não implementar isso direito no front, o usuário vai achar que o sistema está com bug. Já me ligaram por causa disso. A API estava certa, o front não estava tratando o 401.
Refresh token com expiração longa, dias ou semanas, precisa de armazenamento seguro no cliente e precisa ser revogável. Você está de volta ao problema de estado.
Não é impossível de implementar. Só tem mais peças móveis do que parece na primeira vez.
O que acabo usando
Depende do projeto, honestamente.
SPA consumindo a própria API Laravel, mesma origem ou subdomínio controlado: Sanctum com sessão. É a escolha que o próprio Laravel foi desenhado pra oferecer nesse cenário. Funciona, tem suporte ativo, a documentação é boa, e não tem surpresa desagradável depois de seis meses.
API consumida por app mobile ou por múltiplos clientes externos: Sanctum com token de API. Prefiro esse caminho pra API simples porque o modelo é mais fácil de raciocinar e a revogação funciona corretamente, o token fica no banco, você deleta o registro e está feito. Para projetos que já chegam com requisito de JWT por padrão corporativo ou compatibilidade com sistema externo, o tymon/jwt-auth é o que funciona bem no Laravel.
Projeto com autenticação federada, múltiplos serviços que precisam validar autenticação de forma independente sem depender de serviço central: JWT com chave assimétrica. Mas esse cenário é minoria. A maioria dos projetos que recebo não chega nem perto dessa complexidade.
Tem uma coisa que aprendi depois de errar algumas vezes: a escolha de mecanismo de autenticação é menos importante do que a disciplina de implementação. JWT com expiração de um ano e sem refresh é pior do que sessão bem configurada. Sessão sem Redis em ambiente com múltiplos servidores é um bug esperando para acontecer no pior momento possível, geralmente sexta à noite.
A tecnologia você escolhe em dez minutos. A implementação você carrega nos próximos anos.
Desenvolvo APIs e sistemas sob medida com Laravel e PHP. Se você está começando um projeto e quer validar a arquitetura antes de comprometer a equipe, entra em contato pelo gabriels.dev.br.