Cómo crear una API de autenticación en Express.js para reclutadores
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?
- ¿Qué duración debería tener el JWT?
- ¿Cómo implemento refresh tokens con cookies HttpOnly?
- ¿Qué hacer si necesito invalidar tokens inmediatamente?
- ¿Por qué automatizar hashing en Mongoose?
- ¿Qué medidas de seguridad adicionales son críticas en producción?
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.
Respuesta
Usa JWTs cortos (ej. 15–60 minutos) para access tokens. Para sesiones largas, combina con refresh tokens seguros y rotación de tokens.
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.
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.
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.
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.
