Autenticação em Node.js: JWT, Bcrypt e Sessões com Express
Autenticação é o pilar da segurança em aplicações web. Nesta aula, exploraremos três abordagens fundamentais: JWT (JSON Web Tokens), Bcrypt para hash de senhas e gerenciamento de sessões com Express. Compreender quando e como usar cada uma transformará sua capacidade de construir sistemas seguros e escaláveis.
Proteção de Senhas com Bcrypt
Por que não armazenar senhas em texto plano?
Senhas jamais devem ser armazenadas em texto plano no banco de dados. Se comprometido, todos os usuários estarão em risco. O Bcrypt é uma função de hash adaptativa que torna computacionalmente cara a reversão, implementando "salt" automaticamente (um valor aleatório que previne ataques de rainbow table).
const bcrypt = require('bcrypt');
// Durante o registro do usuário
async function registrarUsuario(email, senhaPlana) {
const salt = await bcrypt.genSalt(10); // Gera salt com custo 10
const senhaHash = await bcrypt.hash(senhaPlana, salt);
// Salva no banco: { email, senhaHash }
console.log('Senha armazenada:', senhaHash);
}
// Durante login, valida a senha
async function validarSenha(senhaPlana, senhaHashArmazenada) {
const valida = await bcrypt.compare(senhaPlana, senhaHashArmazenada);
return valida; // true ou false
}
registrarUsuario('user@example.com', 'minhaSenha123');
O custo 10 (padrão) significa que o algoritmo itera 2^10 vezes. Nunca use custo abaixo de 10 em produção. A função compare() é crucial: ela hash a entrada e compara com o hash armazenado, nunca descriptografando.
JWT: Autenticação Stateless
Estrutura e fluxo JWT
JWT (JSON Web Token) é um padrão aberto que permite transmitir informações seguras entre partes. Composto por três seções separadas por pontos: header.payload.signature. Diferente de sessões, não requer armazenamento no servidor, tornando-o ideal para APIs escaláveis.
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
const SECRET = process.env.JWT_SECRET || 'sua_chave_secreta_super_segura';
// Gerar token após login bem-sucedido
function gerarToken(usuario) {
const payload = {
id: usuario.id,
email: usuario.email
};
const token = jwt.sign(payload, SECRET, { expiresIn: '24h' });
return token;
}
// Middleware para verificar token
function verificarToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1]; // Bearer <token>
if (!token) {
return res.status(401).json({ erro: 'Token não fornecido' });
}
try {
const decoded = jwt.verify(token, SECRET);
req.usuario = decoded; // Adiciona ao request
next();
} catch (error) {
res.status(401).json({ erro: 'Token inválido ou expirado' });
}
}
// Rota protegida
app.get('/perfil', verificarToken, (req, res) => {
res.json({ mensagem: `Bem-vindo, ${req.usuario.email}` });
});
Quando usar JWT: APIs RESTful, aplicações mobile, arquiteturas de microsserviços. O cliente armazena o token (localStorage, memory) e o envia a cada requisição. O servidor apenas valida, sem manter estado.
Gerenciamento de Sessões com Express
Sessões no lado do servidor
Sessões mantêm estado no servidor. O Express, combinado com express-session e um store de sessões, cria um cookie no cliente contendo apenas um identificador, enquanto os dados reais ficam no servidor.
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const express = require('express');
const app = express();
// Cliente Redis para armazenar sessões
const redisClient = createClient();
redisClient.connect();
// Configurar sessões
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET || 'chave_secreta',
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS apenas
httpOnly: true, // Não acessível por JavaScript
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000 // 24 horas
}
}));
// Login com sessão
app.post('/login', (req, res) => {
// Após validar credenciais...
req.session.usuarioId = 123;
req.session.email = 'user@example.com';
res.json({ mensagem: 'Login realizado' });
});
// Rota protegida
app.get('/dashboard', (req, res) => {
if (!req.session.usuarioId) {
return res.status(401).json({ erro: 'Não autenticado' });
}
res.json({ mensagem: `Bem-vindo, ${req.session.email}` });
});
// Logout
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ erro: err });
res.json({ mensagem: 'Logout realizado' });
});
});
Quando usar sessões: Aplicações monolíticas, single-page applications (SPAs) server-side, quando o servidor é confiável e centralizado. Redis ou banco de dados armazenam dados da sessão; o cliente recebe apenas um cookie seguro.
Comparação e Escolha
JWT: Stateless, escalável, ideal para APIs e microsserviços. Desvantagem: revogar token é complexo (mantenha blacklist em cache).
Sessões: Stateful, controle total, fácil revogar. Desvantagem: requer infraestrutura de armazenamento.
A escolha depende da arquitetura. Em um monólito com um único servidor, sessões são simples. Em uma arquitetura distribuída com múltiplos servidores/microsserviços, JWT reduz overhead. Uma estratégia híbrida é comum: use JWT para autenticação inicial e sessão para dados sensíveis locais.
Conclusão
Três pontos-chave dominados nesta aula:
-
Bcrypt protege senhas irreversivelmente — sempre hash com salt, nunca armazene em texto plano. Use
bcrypt.compare()para validar, nunca armazene a senha plana. -
JWT oferece autenticação stateless — ideal para APIs, mas exige estratégia para revogação. Token é autocontido e escalável entre múltiplos servidores.
-
Sessões mantêm controle centralizado — simples revogar, mas requerem estado no servidor. Escolha baseando-se na arquitetura e requisitos de escalabilidade.
Combine essas técnicas conforme necessário. Em aplicações reais, JWT para APIs públicas, sessões para SPAs e sempre Bcrypt para senhas.