Cookies, sessões e tokens: mantendo o login na web
Como a web lembra quem você é entre requisições: cookies, sessões no servidor e tokens, com a mecânica e as decisões de segurança de cada abordagem.
Neste artigo
O HTTP não tem memória. Cada requisição que sai do navegador chega ao servidor como se fosse a primeira, sem nenhuma lembrança do que veio antes. Isso levanta uma pergunta que está no coração de qualquer aplicação web com login: se o protocolo esquece tudo a cada requisição, como é que o site sabe que sou eu, requisição após requisição, sem me pedir a senha a cada clique? A resposta está em uma família de técnicas que reintroduzem estado sobre um protocolo sem estado, e entender a mecânica de cada uma é o que separa uma autenticação sólida de uma cheia de brechas.
O tema costuma ser tratado com uma mistura de jargões — cookie, sessão, token, JWT — usados de forma intercambiável, o que gera confusão. Na verdade, esses termos descrevem coisas em camadas diferentes. O cookie é um mecanismo de transporte; a sessão é uma estratégia de onde guardar o estado; o token é um formato de credencial. Dá para combinar cookie com sessão, cookie com token, token sem cookie. Separar essas camadas é o primeiro passo para pensar com clareza sobre o assunto.
Neste artigo vamos construir o entendimento de baixo para cima: primeiro o cookie e como ele viaja, depois a sessão guardada no servidor, depois os tokens autocontidos, e por fim as decisões de segurança que valem para qualquer uma das abordagens. O objetivo é que, ao final, você saiba não só como cada peça funciona, mas por que escolher uma ou outra em cada situação.
O cookie: o carregador de estado#
O cookie é o mecanismo mais antigo e ainda o mais fundamental. Funciona assim: o servidor, ao responder uma requisição, inclui o cabeçalho Set-Cookie com um par de nome e valor. O navegador guarda esse valor e, a partir daí, o reenvia automaticamente no cabeçalho Cookie em toda requisição subsequente para aquele domínio. É esse reenvio automático que resolve a falta de memória do HTTP: o servidor guardou um pedaço de informação no navegador, e o navegador devolve fielmente esse pedaço em cada visita.
A palavra-chave é automático. O desenvolvedor não precisa fazer nada no código do cliente para o cookie ser enviado; o navegador cuida disso sozinho, seguindo as regras de domínio e caminho definidas quando o cookie foi criado. Essa automaticidade é ao mesmo tempo a maior conveniência e o maior risco dos cookies, e é a origem de várias considerações de segurança que veremos adiante.
Os atributos que definem o comportamento do cookie#
Um cookie não é apenas um valor; ele vem com atributos que controlam seu comportamento, e esses atributos são decisões de segurança disfarçadas de configuração.
- O atributo
HttpOnlyimpede que o JavaScript da página leia o cookie. O valor continua sendo enviado nas requisições, mas fica invisível para o código. Isso é essencial para cookies de autenticação, porque, se um atacante conseguir injetar script na página, ele não consegue roubar um cookie marcado assim. - O atributo
Securefaz o cookie só ser enviado sobre conexões cifradas, evitando que ele trafegue em texto claro onde alguém poderia interceptá-lo. - O atributo
SameSitecontrola se o cookie é enviado em requisições que partem de outros sites. Restringir isso é a principal defesa contra um tipo de ataque em que um site malicioso induz o navegador a fazer requisições autenticadas para outro serviço aproveitando o envio automático do cookie. - Os atributos de expiração definem se o cookie dura só enquanto o navegador está aberto ou persiste por um prazo determinado.
A sessão no servidor: guardar o estado do outro lado#
Com o cookie como transporte, surge a primeira grande estratégia: a sessão guardada no servidor. A ideia é que o cookie não carregue os dados do usuário, e sim apenas um identificador — um número aleatório longo e imprevisível. Todos os dados de verdade sobre aquela sessão (quem é o usuário, quando fez login, quais permissões tem) ficam guardados no servidor, indexados por esse identificador. Quando uma requisição chega com o cookie, o servidor pega o identificador, procura os dados correspondentes no seu armazenamento e sabe quem está falando.
A grande vantagem dessa abordagem é o controle. Como o estado real mora no servidor, encerrar uma sessão é trivial: basta apagar o registro correspondente, e o cookie que o cliente ainda tem passa a apontar para o nada. Isso torna o logout imediato e confiável, e permite invalidar sessões em massa quando necessário — por exemplo, expulsar todos os dispositivos de um usuário após uma troca de senha. O servidor é dono absoluto do estado e pode revogá-lo a qualquer momento.
O custo da sessão no servidor#
A contrapartida é que o servidor precisa guardar e consultar esse estado em toda requisição. Isso significa um armazenamento — em memória, em banco, em um cache dedicado — que precisa estar acessível a todos os servidores que atendem o usuário. Em uma aplicação distribuída por várias máquinas, isso exige que o estado da sessão seja compartilhado entre elas, tipicamente por um armazenamento central. É um custo real de infraestrutura, e é justamente esse custo que motivou a popularidade da abordagem alternativa dos tokens autocontidos.
Tokens autocontidos: carregando o estado na credencial#
A segunda grande estratégia inverte a lógica. Em vez de guardar o estado no servidor e mandar só um identificador, o token autocontido carrega os próprios dados dentro de si. Um token desse tipo contém, dentro dele, as informações sobre o usuário — quem é, quando o token foi emitido, quando expira, quais permissões básicas — e, crucialmente, uma assinatura criptográfica gerada pelo servidor.
Essa assinatura é o que torna o mecanismo confiável. O servidor emite o token assinado com um segredo que só ele conhece. Quando o token volta em uma requisição, o servidor verifica a assinatura: se ela confere, o servidor tem certeza de que o conteúdo do token não foi adulterado e foi ele mesmo quem o emitiu. Assim, ele pode confiar nos dados de dentro do token sem precisar consultar nenhum armazenamento — a credencial se valida sozinha. É isso que significa autocontido, e é o que remove a necessidade de guardar estado de sessão no servidor.
O ponto cego dos tokens: revogação#
Essa autonomia tem um preço que é fonte de muitos incidentes de segurança: revogar um token autocontido é difícil. Como o servidor não guarda estado, ele não tem onde marcar "este token não vale mais". Enquanto a assinatura for válida e o prazo não tiver expirado, o token continua sendo aceito, mesmo que o usuário tenha feito logout ou tido as credenciais comprometidas. Não existe um botão simples para invalidá-lo antecipadamente.
A prática lida com isso com um arranjo de dois tokens. Um token de acesso, de vida curta — poucos minutos —, carrega a identidade e é usado nas requisições; e um token de atualização, de vida mais longa, serve apenas para obter novos tokens de acesso. Como o token de acesso expira rápido, o dano de um vazamento é limitado no tempo; e o token de atualização, que é usado com menos frequência, pode ser gerenciado e revogado no servidor, recuperando o controle que os tokens puros perdem. É um equilíbrio entre a leveza de não guardar estado e a necessidade de conseguir cortar o acesso.
Onde guardar o token no cliente#
Uma decisão que gera muito debate é onde o navegador deve guardar um token. A tentação é guardá-lo no armazenamento local do navegador, acessível pelo JavaScript, o que facilita anexá-lo manualmente nas requisições. O problema é que qualquer script que consiga rodar na página — por uma injeção maliciosa — também consegue ler esse armazenamento e roubar o token. É uma superfície de ataque grande.
A alternativa mais segura é guardar a credencial em um cookie com os atributos protetores adequados, especialmente aquele que a esconde do JavaScript. Assim, mesmo que um atacante injete script, ele não consegue ler a credencial. A contrapartida é que cookies exigem cuidado com o ataque de requisição forjada entre sites, que se mitiga com o atributo de restrição de origem e com verificações adicionais. Em resumo: guardar em local acessível ao script é conveniente e arriscado; guardar em cookie protegido é mais seguro e exige atenção a outra classe de ataque. Não há mágica, apenas escolha consciente de quais riscos mitigar.
Decisões de segurança que valem sempre#
Independentemente da estratégia, alguns princípios se aplicam a qualquer sistema de login. As credenciais de sessão precisam ser geradas por um mecanismo criptograficamente forte, imprevisível, nunca por um gerador de números comum que um atacante conseguiria adivinhar. Toda a troca precisa acontecer sobre conexão cifrada, para que a credencial não seja capturada em trânsito. As sessões precisam de um prazo de expiração razoável, equilibrando conveniência e risco. E a autorização — decidir o que o usuário pode fazer — precisa ser verificada no servidor a cada operação, nunca confiando em uma decisão tomada no cliente, que o usuário pode manipular.
Também importa lembrar que autenticação e autorização são coisas distintas. Autenticar é estabelecer quem é o usuário; autorizar é decidir o que ele pode fazer. Um token ou sessão prova a identidade, mas cada ação sensível ainda precisa de uma verificação de permissão do lado do servidor. Confundir os dois — presumir que, porque o usuário está logado, ele pode fazer qualquer coisa — é a raiz de uma classe inteira de falhas em que um usuário acessa dados de outro.
Fechando#
Manter o login na web é o problema de reintroduzir memória em um protocolo que não tem nenhuma. O cookie resolve o transporte, reenviando automaticamente um valor a cada requisição. A sessão no servidor guarda o estado do lado seguro e troca conveniência de revogação por custo de armazenamento. O token autocontido carrega o estado na própria credencial assinada, ganha escala ao dispensar armazenamento e paga com a dificuldade de revogar, contornada pelo par de tokens de vida curta e longa.
Entender essas camadas separadamente — transporte, onde guardar o estado, formato da credencial — permite combiná-las com intenção em vez de copiar receitas sem saber o que cada uma protege. E, por baixo de todas elas, ficam os mesmos princípios inegociáveis: credenciais imprevisíveis, tráfego cifrado, prazos sensatos, credencial escondida do script quando possível, e autorização sempre reverificada no servidor. Com esse mapa na cabeça, decidir como o seu sistema vai lembrar quem é o usuário deixa de ser uma escolha às cegas e passa a ser uma engenharia de compensações que você entende e controla.