29 de set. de 2025·8 min de leitura

Gerenciamento de sessões para apps web: cookies vs JWTs vs refresh

Gerenciamento de sessões para apps web comparado: sessões por cookie, JWTs e refresh tokens, com modelos de ameaça concretos e requisitos realistas de logout.

Gerenciamento de sessões para apps web: cookies vs JWTs vs refresh

O que o gerenciamento de sessão realmente faz

Uma sessão é como seu app responde a uma pergunta depois que alguém faz login: "Quem é você agora?". Uma vez que essa resposta seja confiável, o app decide o que o usuário pode ver, o que pode alterar e quais ações devem ser bloqueadas.

"Manter-se logado" também é uma escolha de segurança. Você decide por quanto tempo uma identidade do usuário deve permanecer válida, onde fica a prova dessa identidade e o que acontece se essa prova for copiada.

A maioria das arquiteturas de web app usa três blocos básicos:

  • Sessões no servidor baseadas em cookie: o navegador armazena um cookie, e o servidor consulta a sessão a cada requisição.
  • JWT access tokens: o cliente envia um token assinado que o servidor pode verificar sem consultar o banco de dados.
  • Refresh tokens: uma credencial de vida mais longa usada para obter novos access tokens de curta duração.

Esses não são estilos rivais tanto quanto diferentes formas de lidar com os mesmos trade-offs: velocidade vs controle, simplicidade vs flexibilidade, e "podemos invalidar isso agora?" vs "isso expira por si só?".

Uma forma útil de avaliar qualquer design: se um atacante roubar o que seu app usa como prova (um cookie ou token), o que ele pode fazer e por quanto tempo? Sessões por cookie frequentemente vencem quando você precisa de controle forte no servidor, como logout forçado ou bloqueio instantâneo. JWTs podem ser bons para verificações sem estado entre serviços, mas ficam complicados quando você precisa de revogação imediata.

Nenhuma opção vence em todos os casos. A abordagem certa depende do seu modelo de ameaça, de quão estritas são as exigências de logout e de quanta complexidade sua equipe consegue manter.

Modelos de ameaça que mudam a resposta certa

Um bom desenho de sessão depende menos do "melhor" tipo de token e mais dos ataques que você realmente precisa resistir.

Se um atacante rouba dados do armazenamento do navegador (como localStorage), access tokens JWT são fáceis de capturar porque o JavaScript da página pode lê-los. Um cookie roubado é diferente: se ele estiver marcado como HttpOnly, o código normal da página não pode lê-lo, então ataques simples de "captura de token" ficam mais difíceis. Mas se o atacante tem o dispositivo (laptop perdido, malware, computador compartilhado), cookies ainda podem ser copiados do perfil do navegador.

XSS (código de atacante rodando na sua página) muda tudo. Com XSS, o invasor pode não precisar roubar nada: ele pode usar a sessão já autenticada da vítima para executar ações. Cookies HttpOnly ajudam a impedir a leitura de segredos de sessão, mas não impedem que o atacante faça requisições a partir da página.

CSRF (um site diferente disparando ações indesejadas) ameaça principalmente sessões baseadas em cookie, porque os navegadores enviam cookies automaticamente. Se você depende de cookies, precisa de defesas claras contra CSRF: configurações intencionais de SameSite, tokens anti-CSRF e cuidado no tratamento de requisições que mudam estado. JWTs enviados no cabeçalho Authorization ficam menos expostos ao CSRF clássico, mas ainda estão vulneráveis a XSS se forem armazenados onde o JavaScript pode lê-los.

Ataques de replay (reaproveitamento de uma credencial roubada) são onde sessões no servidor se destacam: você pode invalidar um ID de sessão imediatamente. JWTs de curta duração reduzem o tempo útil de replay, mas não impedem o replay enquanto o token for válido.

Dispositivos compartilhados e celulares perdidos transformam "sair" em um modelo de ameaça real. As decisões costumam se resumir a perguntas como: o usuário pode forçar logout em outros dispositivos, quão rápido isso deve ter efeito, o que acontece se um refresh token for roubado e você permite sessões "lembrar de mim"? Muitas equipes também tratam o acesso de funcionários com padrão mais rigoroso do que o de clientes, alterando timeouts e expectativas de revogação.

Sessões baseadas em cookie são a configuração clássica. Após o login, o servidor cria um registro de sessão (geralmente um ID mais campos como ID do usuário, hora de criação e expiração). O navegador armazena apenas o ID da sessão em um cookie. A cada requisição, o navegador envia esse cookie de volta, e o servidor consulta a sessão para decidir quem é o usuário.

A grande vantagem de segurança é o controle. A sessão é validada no servidor a cada vez. Se você precisa expulsar alguém, você exclui ou desativa o registro de sessão no servidor e ele para de funcionar imediatamente, mesmo que o usuário ainda tenha o cookie.

Grande parte da proteção vem das configurações do cookie:

  • HttpOnly: impede que o JavaScript leia o cookie.
  • Secure: envia o cookie apenas via HTTPS.
  • SameSite: limita quando o navegador envia o cookie em requisições cross-site.

Onde você armazena o estado da sessão afeta a escalabilidade. Manter sessões na memória do app é simples, mas falha quando você executa múltiplos servidores ou reinicia com frequência. Um banco de dados funciona bem para durabilidade. Redis é comum quando você quer buscas rápidas e muitas sessões ativas. O ponto é o mesmo: o servidor precisa encontrar e validar a sessão a cada requisição.

Sessões por cookie são uma boa escolha quando você precisa de comportamento de logout estrito, como painéis administrativos ou portais de clientes onde um admin precisa forçar logout após uma alteração de função. Se um funcionário sai, desativar as sessões no servidor encerra o acesso imediatamente, sem esperar tokens expirarem.

Access tokens JWT: pontos fortes e arestas cortantes

Um JWT (JSON Web Token) é uma string assinada que carrega algumas claims sobre o usuário (como ID do usuário, função, tenant) mais um tempo de expiração. Sua API verifica localmente a assinatura e a expiração, sem chamar o banco, e então autoriza a requisição.

É por isso que JWTs são populares em produtos API-first, apps móveis e sistemas onde múltiplos serviços precisam validar a mesma identidade. Se você tem várias instâncias de backend, cada uma pode verificar o mesmo token e obter a mesma resposta.

Pontos fortes

Access tokens JWT são rápidos de checar e fáceis de propagar entre chamadas de API. Se seu frontend chama muitos endpoints, um access token de curta duração pode manter o fluxo simples: verifica assinatura, lê o ID do usuário e continua.

Exemplo: um portal de clientes chama "List invoices" e "Update profile" em serviços separados. Um JWT pode carregar o ID do cliente e uma role como customer, de modo que cada serviço possa autorizar a requisição sem consultar uma sessão a cada vez.

Arestas cortantes

A maior troca é a revogação. Se um token é válido por uma hora, geralmente ele é aceito por toda parte naquela hora, mesmo se o usuário clicar em "log out" ou um admin desativar a conta, a menos que você adicione verificações extras no servidor.

JWTs também vazam de formas corriqueiras. Pontos comuns de falha incluem localStorage (XSS pode ler), memória do navegador (extensões maliciosas), logs e relatórios de erro, proxies e ferramentas de analytics que capturam cabeçalhos, e tokens copiados em chats de suporte ou screenshots.

Por isso, access tokens JWT funcionam melhor para acesso de curta duração, não para "login eterno". Mantenha-os mínimos (sem dados pessoais sensíveis dentro), com expiração curta, e assuma que um token roubado será utilizável até expirar.

Refresh tokens: tornando setups com JWT viáveis

Envie um portal de clientes seguro
Gere um web app e backend prontos para produção a partir de um único projeto.
Criar app

Access tokens JWT devem ser de curta duração. Isso é bom para segurança, mas cria um problema prático: usuários não deveriam ter que fazer login de novo a cada poucos minutos. Refresh tokens resolvem isso permitindo que o app obtenha silenciosamente um novo access token quando o antigo expira.

Onde você guarda o refresh token importa ainda mais do que onde guarda o access token. Em um web app no navegador, o padrão mais seguro é um cookie HttpOnly e Secure para que o JavaScript não possa ler. Local storage é mais fácil de implementar, mas também mais fácil de roubar se houver qualquer bug de XSS. Se seu modelo de ameaça inclui XSS, evite colocar segredos de longa duração em locais acessíveis ao JavaScript.

A rotação é o que torna refresh tokens viáveis em sistemas reais. Em vez de usar o mesmo refresh token por semanas, você o troca a cada uso: o cliente apresenta o refresh token A, o servidor emite um novo access token mais o refresh token B, e o refresh token A é invalidado.

Uma configuração simples de rotação geralmente segue algumas regras:

  • Mantenha access tokens curtos (minutos, não horas).
  • Armazene refresh tokens no servidor com status e último uso.
  • Rode a rotação a cada refresh e invalide o token anterior.
  • Vincule refresh tokens a um dispositivo ou navegador quando possível.
  • Registre eventos de refresh para investigar abuso.

Detecção de reutilização é o alarme chave. Se o refresh token A já foi trocado, mas você o vê de novo mais tarde, suponha que foi copiado. Uma resposta comum é revogar toda a sessão (e frequentemente todas as sessões desse usuário) e exigir novo login, porque você não sabe qual cópia é a legítima.

Para logout, você precisa de algo que o servidor possa fazer cumprir. Isso geralmente significa uma tabela de sessões (ou uma lista de revogação) que marca refresh tokens como revogados. Access tokens podem ainda funcionar até expirarem, mas você pode manter essa janela pequena deixando os access tokens com curta duração.

Requisitos de logout e o que é realmente aplicável

Logout parece simples até você defini-lo. Normalmente há dois pedidos diferentes: "sair deste dispositivo" (um navegador ou um telefone) e "sair de todos os lugares" (todas as sessões ativas em todos os dispositivos).

Há também a questão de tempo. "Logout imediato" significa que o app para de aceitar a credencial agora. "Logout após expiração" significa que o app para de aceitar quando a sessão ou token atual expirar naturalmente.

Com sessões por cookie, logout imediato é direto porque o servidor é dono da sessão. Você apaga o cookie no cliente e invalida o registro de sessão no servidor. Se alguém copiou o valor do cookie antes, a rejeição no servidor é o que realmente faz o logout valer.

Com autenticação apenas por JWT (access tokens sem estado e sem consulta ao servidor), você não pode garantir logout imediato. Um JWT roubado permanece válido até expirar, porque o servidor não tem como checar "este token foi revogado?". Você pode adicionar uma denylist, mas aí está mantendo estado e consultando-o, o que tira muita da simplicidade original.

Um padrão prático é tratar access tokens como de curta duração e aplicar logout por meio dos refresh tokens. O access token pode "correr" por alguns minutos, mas o refresh token é o que mantém a sessão viva. Se um laptop for roubado, revogar a família de refresh tokens corta o acesso futuro rapidamente.

O que você pode prometer realisticamente aos usuários:

  • Sair deste dispositivo: revogar aquela sessão ou refresh token e apagar cookies ou armazenamento local.
  • Sair de todos os dispositivos: revogar todas as sessões ou todas as famílias de refresh tokens da conta.
  • Efeito "imediato": garantido com sessões no servidor, realizado com melhores esforços com access tokens até expirarem.
  • Eventos de logout forçado: mudança de senha, conta desativada, rebaixamento de função.

Para mudanças de senha e desativação de conta, não confie que "o usuário vai se desconectar". Armazene uma versão de sessão por conta (ou um timestamp "token válido após"). Em cada refresh (e às vezes em cada requisição), compare. Se mudou, negue e peça novo login.

Passo a passo: escolhendo uma abordagem de sessão para seu app

Implemente onde sua equipe roda
Implante no AppMaster Cloud, AWS, Azure, Google Cloud, ou exporte o código-fonte.
Implantar agora

Se você quer manter o design de sessões simples, decida as regras primeiro e só então escolha a mecânica. A maioria dos problemas começa quando equipes escolhem JWTs ou cookies porque são populares, não porque combinam com os riscos e exigências de logout.

Comece listando todos os lugares onde um usuário faz login. Um app de navegador se comporta diferente de um app nativo, uma ferramenta administrativa interna ou uma integração de parceiro. Cada caso muda o que pode ser armazenado com segurança, como logins são renovados e o que "logout" deve significar.

Uma ordem prática que funciona para a maioria das equipes:

  1. Liste seus clientes: web, iOS/Android, ferramentas internas, acesso de terceiros.
  2. Escolha um modelo de ameaça padrão: XSS, CSRF, dispositivo roubado.
  3. Decida o que o logout deve garantir: este dispositivo, todos os dispositivos, logout forçado por admin.
  4. Escolha um padrão base: sessões baseadas em cookie (servidor lembra) ou access token + refresh token.
  5. Defina timeouts e regras de resposta: expiração por inatividade vs expiração absoluta, e o que fazer quando detectar reutilização suspeita.

Depois documente as promessas exatas do seu sistema. Exemplo: "Sessões web expiram após 30 minutos inativas ou 7 dias absolutos. Admin pode forçar logout em até 60 segundos. Celular perdido pode ser desativado remotamente." Essas frases importam mais do que a biblioteca que você usa.

Por fim, adicione monitoramento que combine com seu padrão. Para setups com tokens, um sinal forte é a reutilização de refresh token (o mesmo refresh token usado duas vezes). Trate isso como provável furto, revogue a família de sessão e alerte o usuário.

Erros comuns que levam a tomada de conta de contas

Torne o logout forçado aplicável
Mantenha o acesso da equipe sob controle com regras de revogação de sessão que você pode aplicar.
Começar

A maioria das tomadas de contas não é por "hacks inteligentes". São vitórias simples causadas por erros previsíveis no gerenciamento de sessões. Um bom manuseio de sessão é basicamente não dar aos atacantes uma maneira fácil de roubar ou reproduzir credenciais.

Uma armadilha comum é colocar access tokens em localStorage e esperar nunca ter XSS. Se qualquer script rodar na sua página (uma dependência insegura, um widget injetado, um comentário armazenado), ele pode ler o localStorage e enviar o token. Cookies com a flag HttpOnly reduzem esse risco porque o JavaScript não pode lê-los.

Outra armadilha é tornar JWTs de longa duração para evitar refresh tokens. Um access token de 7 dias é uma janela de reutilização de 7 dias se vazar. Um access token curto mais um refresh token bem gerido é mais difícil de abusar, especialmente quando você pode cortar o refresh.

Cookies trazem seu próprio tiro no pé: esquecer defesas contra CSRF. Se seu app usa sessões por cookie e você aceita requisições que mudam estado sem proteção CSRF, um site malicioso pode enganar um navegador autenticado a enviar requisições válidas.

Outros erros que aparecem após análises de incidentes:

  • Refresh tokens nunca rodam, ou rodam mas você não detecta reutilização.
  • Você suporta múltiplos métodos de login (sessão por cookie e bearer token) mas a regra do servidor "qual ganha" é confusa.
  • Tokens acabam em logs (console do navegador, eventos de analytics, logs de requisição do servidor), onde são copiados e retidos.

Um exemplo concreto: um agente de suporte cola um "debug log" em um ticket. O log inclui um cabeçalho Authorization. Quem tem acesso ao ticket pode reproduzir esse token e agir como o agente. Trate tokens como senhas: não os imprima, não os armazene e mantenha-os de curta duração.

Verificações rápidas antes de lançar

A maioria dos bugs de sessão não é sobre criptografia sofisticada. É sobre uma flag faltando, um token que vive tempo demais ou um endpoint que deveria exigir reauth.

Antes do release, faça uma rápida revisão focada no que um atacante pode fazer com um cookie ou token roubado. É uma das maneiras mais rápidas de melhorar a segurança sem reescrever toda a autenticação.

Checklist pré-lançamento

Passe por estas verificações em staging e depois de novo em produção:

  • Mantenha access tokens curtos (minutos) e confirme que a API realmente os rejeita após a expiração.
  • Trate refresh tokens como senhas: armazene-os onde o JavaScript não possa ler, se possível, envie-os apenas ao endpoint de refresh e rode a rotação a cada uso.
  • Se usar cookies para autenticação, verifique flags: HttpOnly ligado, Secure ligado e SameSite configurado intencionalmente. Confirme também que o escopo do cookie (domínio e path) não é mais amplo que o necessário.
  • Se cookies autenticam requisições, adicione defesas contra CSRF e confirme que endpoints que mudam estado falham sem o sinal CSRF.
  • Faça revogação real: após reset de senha ou desativação de conta, sessões existentes devem parar de funcionar rapidamente (exclusão no servidor, invalidação de refresh tokens ou verificação de "versão de sessão").

Depois disso, teste as promessas de logout. "Sair" muitas vezes significa "remover sessão local", mas os usuários esperam mais.

Um teste prático: faça login no laptop e no celular, mude a senha. O laptop deve ser forçado a sair na próxima requisição, não horas depois. Se você oferece "sair de todos os lugares" e uma lista de dispositivos, confirme que cada dispositivo corresponde a um registro de sessão ou refresh token distinto que você pode revogar.

Exemplo: um portal de clientes com contas de staff e logout forçado

Adicione lógica de detecção de reutilização
Acompanhe o uso de refresh e reaja a reutilizações com processos de negócio drag-and-drop.
Configurar

Imagine uma pequena empresa com um portal web de clientes (clientes checam faturas, abrem tickets) e um app móvel para equipe (serviços, notas, fotos). A equipe às vezes trabalha em subsolos sem sinal, então o app precisa funcionar offline por um tempo. Admins também querem um grande botão vermelho: se um tablet for perdido ou um contratado sair, eles podem forçar logout.

Adicione três ameaças comuns: tablets compartilhados em vans (alguém esquece de sair), phishing (um funcionário digita credenciais em uma página falsa) e um eventual bug de XSS no portal (um script roda no navegador e tenta roubar o que puder).

Uma configuração prática aqui é access tokens de curta duração mais refresh tokens rotativos, com revogação no servidor. Isso dá chamadas de API rápidas e tolerância ao offline, permitindo ainda que admins cortem sessões.

Isso pode ficar assim:

  • Vida do access token: 5 a 15 minutos.
  • Rotação do refresh token: a cada refresh retorna um novo refresh token e o anterior é invalidado.
  • Armazene refresh tokens com segurança: na web, mantenha o refresh token em um cookie HttpOnly e Secure; no mobile, mantenha-o no armazenamento seguro do SO.
  • Acompanhe refresh tokens no servidor: armazene um registro do token (usuário, dispositivo, hora de emissão, último uso, flag de revogado). Se um token rotacionado for reutilizado, trate como furto e revogue toda a cadeia.

Logout forçado fica aplicável: o admin revoga o registro de refresh token daquele dispositivo (ou de todos os dispositivos do usuário). O dispositivo roubado pode continuar usando o access token atual até ele expirar, mas não pode obter um novo. Assim, o tempo máximo para cortar completamente o acesso é a vida do seu access token.

Para um dispositivo perdido, defina a regra em linguagem simples: "Em até 10 minutos, o app vai parar de sincronizar e exigir login novamente." O trabalho offline pode permanecer no dispositivo, mas o próximo sync online deve falhar até o usuário fazer login.

Próximos passos: implementar, testar e manter simples

Escreva o que "logout" significa em linguagem de produto. Por exemplo: "Fazer logout remove acesso neste dispositivo", "Fazer logout em todos os lugares expulsa todos os dispositivos em até 1 minuto" ou "Alterar sua senha encerra sessões em outros dispositivos". Essas promessas decidem se você precisa de estado no servidor, listas de revogação ou tokens de curta duração.

Transforme as promessas em um pequeno plano de testes. Bugs de token e sessão costumam passar em demonstrações felizes e falhar na vida real (modo de suspensão, redes instáveis, múltiplos dispositivos).

Checklist prático de testes

Execute testes que cubram os casos complicados:

  • Expiração: o acesso para quando o access token ou sessão expira, mesmo com o navegador aberto.
  • Revogação: após "sair de todos os lugares", a credencial antiga falha na próxima requisição.
  • Rotação: a rotação de refresh token emite um novo refresh token e invalida o anterior.
  • Detecção de reutilização: reproduzir um refresh token antigo dispara um bloqueio.
  • Multi-dispositivo: regras para "apenas dispositivo atual" vs "todos os dispositivos" são aplicadas e a UI reflete isso.

Após os testes, faça um ensaio simples de ataque com sua equipe. Escolha três cenários e percorra-os end-to-end: um bug de XSS que consegue ler tokens, uma tentativa de CSRF contra sessões por cookie e um telefone roubado com sessão ativa. Você está verificando se o design bate com as promessas.

Se precisar ir rápido, reduza código customizado. AppMaster (appmaster.io) é uma opção quando você quer um backend gerado pronto para produção mais apps web e nativos, para manter regras como expiração, rotação e logout forçado consistentes entre os clientes.

Agende uma revisão pós-lançamento. Use tickets de suporte reais e incidentes para ajustar timeouts, limites de sessão e comportamento de "sair de todos os lugares", e então rode o mesmo checklist para evitar regressões silenciosas.

Fácil de começar
Criar algo espantoso

Experimente o AppMaster com plano gratuito.
Quando estiver pronto, você poderá escolher a assinatura adequada.

Comece