Uma das decisões de arquitetura mais críticas ao desenhar uma API ou sistema web é a estratégia de persistência de identidade. Como o protocolo HTTP é, por definição, stateless, o servidor não retém contexto entre duas requisições diferentes. Para manter um usuário “logado”, precisamos contornar essa limitação.

Existem duas filosofias dominantes para resolver isso: a Stateful (baseada em Sessão/Referência) e a Stateless (baseada em Tokens/Valor). A escolha entre elas impacta diretamente a escalabilidade horizontal, a complexidade da infraestrutura e a segurança da aplicação.

Vamos analisar as diferenças técnicas e implementações usando Express, OK?

1. Arquitetura Stateful (Sessions)

Server-side Storage (Referência)

No modelo Stateful, o servidor mantém o estado da sessão ativo. O cliente recebe apenas um identificador opaco (Session ID), geralmente armazenado em um Cookie HttpOnly. Esse ID não contém dados de negócio; ele é apenas um ponteiro para um registro armazenado no servidor (em memória, Redis ou Banco de Dados).

O Fluxo Técnico

  1. O cliente envia credenciais.
  2. O servidor valida, cria um registro na memória e retorna o ID da sessão.
  3. Nas próximas requisições, o browser envia o Cookie automaticamente.
  4. O servidor recebe o ID, faz uma busca no banco para recuperar o contexto e popula o objeto da requisição.

Implementação em Express (com Redis Store):

PHP
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { 
    secure: true, 
    httpOnly: true, 
    maxAge: 1000 * 60 * 60 * 24 
  } 
}));

app.post('/login', (req, res) => {
  req.session.userId = user.id;
  req.session.role = user.role; 
  res.send('Autenticado via Sessão Stateful');
});

Trade-offs

  • Controle de Acesso (Pró): Permite revogação imediata. Se você apagar a chave no Redis, o usuário é desconectado no próximo request. Ideal para painéis administrativos e aplicações sensíveis.
  • Acoplamento de Infraestrutura (Contra): Exige um Session Store centralizado e rápido (geralmente Redis). Em arquiteturas com Load Balancers, você não pode usar a memória RAM local do processo Node, ou o usuário perderá a sessão ao cair em outro servidor.

2. Arquitetura Stateless (JWT)

Client-side Storage (Valor)

No modelo Stateless, o servidor não armazena o contexto do usuário. O estado é encapsulado em um token assinado criptograficamente (JWT) e entregue ao cliente. O cliente deve reenviar esse token (no Header Authorization ou Cookie) a cada requisição. O servidor valida a assinatura matemática para confiar nos dados.

O Fluxo Técnico

  1. O cliente envia credenciais.
  2. O servidor valida, gera um JSON com os dados (Payload), assina com uma chave privada e retorna o token.
  3. O servidor não salva nada.
  4. Nas próximas requisições, o servidor gasta CPU para verificar se a assinatura bate com o payload enviado. Se bater, o token é válido.

Implementação em Express (com jsonwebtoken):

PHP
const jwt = require('jsonwebtoken');

app.post('/login', (req, res) => {

  const payload = { 
    sub: user.id, 
    role: user.role,
    iss: 'meu-app'
  };

  const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

  res.json({ token });
});

const verifyToken = (req, res, next) => {
  const token = req.headers['authorization']?.split(' ')[1];
  
  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
    if (err) return res.sendStatus(403);
    req.user = decoded;
    next();
  });
};

Trade-offs

  • Escalabilidade Horizontal (Pró): Como a validação é autocontida (CPU-bound) e não requer I/O de banco de dados, qualquer instância do servidor que tenha a chave secreta pode validar o token. Facilita muito a arquitetura de microsserviços.
  • Problema da Revogação (Contra): Um token válido é válido até expirar. Para banir um usuário antes da expiração, você precisa implementar uma Blacklist ou Allowlist em um banco rápido (Redis), o que, ironicamente, reintroduz o estado na aplicação.

Comparativo Técnico

Visualizando o fluxo de dados e onde ocorre o gargalo em cada arquitetura:

CritérioStateful (Session)Stateless (JWT)
Mecanismo de PersistênciaServer-side (Redis/DB)Client-side (Token assinado)
Custo no ServidorI/O e Memória (Network Trip ao Redis)CPU (Cálculo criptográfico)
Revogação (Logout) via server-sideTrivial e ImediataComplexa (Depende do exp ou Blacklist)
Tamanho da RequestLeve (apenas SessionID)Pesado (todo o Payload no Header)
Segurança (XSS/CSRF)Cookies HttpOnly mitigam XSS, exige proteção CSRFLocalStorage vulnerável a XSS. Cookies exigem CSRF

Conclusão: Qual escolher?

A decisão não deve ser baseada em “modernidade”, mas nos requisitos não-funcionais do sistema.

  1. Escolha Stateful (Sessions) para monólitos, aplicações renderizadas no servidor (SSR), sistemas internos, backoffices ou aplicações bancárias onde a capacidade de invalidar uma sessão instantaneamente é um requisito de segurança inegociável.
  2. Escolha Stateless (JWT) para arquiteturas distribuídas (Microsserviços), onde múltiplos serviços precisam saber quem é o usuário sem consultar um banco centralizado, ou para APIs públicas consumidas por terceiros e dispositivos móveis.

Não existe uma escolha universalmente correta entre arquiteturas Stateful e Stateless. A decisão deve ser orientada pelos requisitos não funcionais do sistema, como necessidade de revogação imediata, escalabilidade horizontal, latência e complexidade operacional. Em muitos cenários, uma abordagem híbrida é a mais adequada: sessões stateful para controle rígido de acesso e invalidação imediata, combinadas com tokens stateless de curta duração para comunicação entre serviços. Tratar identidade como uma decisão de arquitetura, e não apenas de implementação, é fundamental para a sustentabilidade e segurança da aplicação ao longo do tempo.

Veja mais artigos sobre esse assunto aqui.