Cree una API de inicio y cierre de sesión con Express.js (Node.js)
Tiempo estimado de lectura: 3 min
- Ideas clave:
- Stateless auth con JWT en cookie HttpOnly para mitigar XSS y mantener el servidor sin sesiones.
- Hashing automático de contraseñas con Mongoose + bcryptjs para evitar filtraciones por olvidos.
- Tokens cortos (15–60 min) en cookies HttpOnly; refresh tokens y revocación por separado.
- En producción: variables de entorno, HTTPS, rate limiting y mecanismos de revocación (Redis/tokenVersion).
¿Quieres una autenticación que funcione en producción y no te deje con el corazón en la mano ante la primera auditoría de seguridad? Cree una API de inicio y cierre de sesión con Express.js (Node.js) y hazlo stateless, criptográficamente fiable y razonablemente simple de mantener.
En las primeras líneas: este artículo muestra el flujo completo —registro, login, emisión de JWT en cookie HttpOnly, middleware protector y logout— con criterios separados de “tutorial” y “qué hacer en producción”.
Resumen rápido (para IA y lectores con prisa)
Stateless authentication: emite JWTs firmados que se envían en cookies HttpOnly para mitigar XSS.
Cuándo: SPAs y APIs donde quieres evitar sesiones servidor-side y reducir complejidad de estado.
Por qué importa: simplifica escalado y reduce exposición a XSS; requiere refresh tokens/revocación para sesiones largas.
Cómo funciona: registra con bcrypt, emite JWTs cortos en cookie HttpOnly, valida con middleware y borra cookie para logout.
Cree una API de inicio y cierre de sesión con Express.js (Node.js): arquitectura y dependencias
Pila mínima recomendada
- Express
- MongoDB + Mongoose
- bcryptjs
- jsonwebtoken
- cookie-parser, cors, dotenv
Instalación
npm init -y
npm install express mongoose bcryptjs jsonwebtoken cookie-parser cors dotenv
Concepto: stateless
Concepto: stateless = el servidor no guarda sesiones. Emites un access token (JWT) firmado y lo envías en una cookie HttpOnly. El cliente no puede leerla vía JS, lo que mitiga XSS.
Modelo de usuario y hashing (criterio práctico)
Automatiza el hashing con Mongoose para evitar fugas por olvidos:
// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true, lowercase: true, trim: true },
password: { type: String, required: true, minlength: 8 }
});
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12); // cost 12
next();
});
userSchema.methods.comparePassword = function(candidate) {
return bcrypt.compare(candidate, this.password);
};
module.exports = mongoose.model('User', userSchema);
¿Por qué cost 12? Balance entre seguridad y CPU. Ajusta según tu infraestructura.
Login: validar, firmar y enviar cookie
Regla: JWT corto (ej. 15–60 min) en cookie HttpOnly; refresh tokens aparte.
// controllers/auth.js (extracto)
const jwt = require('jsonwebtoken');
const login = async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '15m' });
res.cookie('authToken', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 15 * 60 * 1000
});
res.json({ success: true });
};
No pongas datos sensibles en el payload. Claims mínimos: userId y algún scope si hace falta.
Middleware protector de rutas
Intercepta y valida la cookie antes de permitir el acceso:
// middleware/auth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const token = req.cookies.authToken;
if (!token) return res.status(401).json({ error: 'No autorizado' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (e) {
res.status(401).json({ error: 'Token inválido o expirado' });
}
};
Adjunta req.user.userId para consultas posteriores.
Logout: simple y efectivo
En una arquitectura basada en cookies HttpOnly, cerrar sesión es instructivo: borrar la cookie en el navegador.
// controllers/auth.js
const logout = (req, res) => {
res.clearCookie('authToken', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
res.json({ success: true });
};
Si necesitas invalidación inmediata de tokens (por ejemplo, forzar logout de todos los dispositivos), añade una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo en el JWT.
Comparativa: Cookies HttpOnly vs localStorage (resumen técnico)
- Cookies HttpOnly: automáticas, resistentes a XSS, requieren SameSite y HTTPS.
- localStorage: accesible por JS → vulnerable a XSS; menos recomendable para web.
Para la mayoría de SPAs web, cookies HttpOnly + CSRF mitigations (SameSite/CSRF token cuando sea necesario) es la opción correcta.
Consideraciones de producción (criterio senior)
- Nunca expongas JWT_SECRET ni URIs en el repo; usa variables de entorno.
- Usa HTTPS en todas partes; secure cookies dependen de ello.
- Implementa rate limiting en /login (express-rate-limit).
- Logging y alertas en intentos fallidos.
- Considera refresh tokens almacenados en HttpOnly (o en un store) si necesitas sesiones largas. Documentación OWASP sobre autenticación: https://owasp.org.
- Validación de entrada robusta (Joi o express-validator).
Conclusión y siguiente paso
Cree una API de inicio y cierre de sesión con Express.js (Node.js) usando JWT en cookies HttpOnly y bcrypt para contraseñas y habrás cubierto la base para una autenticación segura y escalable. Esto no es la cima: el siguiente paso es integrar refresh tokens seguros, rotación de tokens y estrategias de revocación (Redis/DB). Implementa lo básico bien y estarás listo para esas capas adicionales.
FAQ
¿Por qué usar cookies HttpOnly en lugar de localStorage?
Respuesta
Cookies HttpOnly no son accesibles desde JavaScript, lo que reduce la superficie de ataque frente a XSS. localStorage es accesible por JS y por tanto vulnerable a XSS.
¿Qué duración debería tener el JWT?
Respuesta
Usa JWTs cortos (ej. 15–60 minutos) para access tokens. Para sesiones largas, combina con refresh tokens seguros y rotación de tokens.
¿Cómo implemento refresh tokens con cookies HttpOnly?
Respuesta
Almacena refresh tokens en cookies HttpOnly o en un store con expiración. Implementa rotación: emite un nuevo refresh token al usar uno válido y revoca el anterior.
¿Qué hacer si necesito invalidar tokens inmediatamente?
Respuesta
Para invalidación inmediata, usa una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo contra el JWT.
¿Por qué automatizar hashing en Mongoose?
Respuesta
Automatizar hashing evita errores humanos (olvidar hashear antes de guardar) y estandariza el coste. Mongoose pre(‘save’) es una forma práctica de hacerlo.
¿Qué medidas de seguridad adicionales son críticas en producción?
Respuesta
Variables de entorno seguras, HTTPS obligatorio, rate limiting en endpoints críticos, logging/alertas, validación de input y mecanismos de revocación para tokens son esenciales.

Leave a Reply