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
- O cliente envia credenciais.
- O servidor valida, cria um registro na memória e retorna o ID da sessão.
- Nas próximas requisições, o browser envia o Cookie automaticamente.
- 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):
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
- O cliente envia credenciais.
- O servidor valida, gera um JSON com os dados (Payload), assina com uma chave privada e retorna o token.
- O servidor não salva nada.
- 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):
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ério | Stateful (Session) | Stateless (JWT) |
| Mecanismo de Persistência | Server-side (Redis/DB) | Client-side (Token assinado) |
| Custo no Servidor | I/O e Memória (Network Trip ao Redis) | CPU (Cálculo criptográfico) |
| Revogação (Logout) via server-side | Trivial e Imediata | Complexa (Depende do exp ou Blacklist) |
| Tamanho da Request | Leve (apenas SessionID) | Pesado (todo o Payload no Header) |
| Segurança (XSS/CSRF) | Cookies HttpOnly mitigam XSS, exige proteção CSRF | LocalStorage 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.
- 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.
- 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.