Módulo 6·practica·2h
Objetivos de aprendizaje
- Comprender por qué HTTP es stateless y para qué sirven las sesiones
- Configurar express-session con opciones de seguridad
- Implementar login y logout con req.session
- Proteger rutas con un middleware de autenticación
- Diferenciar sesiones de cookies y de JWT
Sesiones y Autenticación con Express
HTTP es stateless
El protocolo HTTP no guarda estado entre requests. Cada solicitud es independiente y el servidor no recuerda quién hizo la solicitud anterior.
code
Request 1: GET /productos → Servidor no sabe quién eres
Request 2: POST /login {usuario} → Servidor autentica, pero en la
Request 3: GET /admin → próxima request ya no lo recuerdaLas sesiones resuelven este problema guardando datos en el servidor y enviando al cliente solo un ID de sesión (en una cookie):
code
Cliente Servidor
│ │
│──── POST /login {user, pass} ──────▶│
│ │ Crea sesión: sess_abc123
│◀─── Set-Cookie: sessID=abc123 ──────│ req.session.usuario = "Ana"
│ │
│──── GET /admin (Cookie: abc123) ───▶│
│ │ Busca sesión abc123 → OK
│◀─── 200 OK (Panel admin) ──────────│Instalación y configuración
bash
npm install express-sessionjavascript
// server.js
import express from "express";
import session from "express-session";
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(
session({
secret: process.env.SESSION_SECRET ?? "cambia-esto-en-produccion",
resave: false, // No re-guardar si no hubo cambios
saveUninitialized: false, // No crear sesión si no hay datos
cookie: {
maxAge: 1000 * 60 * 60, // 1 hora en milisegundos
httpOnly: true, // La cookie no es accesible desde JS del cliente
secure: process.env.NODE_ENV === "production", // Solo HTTPS en producción
sameSite: "lax", // Protección CSRF básica
},
})
);Usuarios mock para la demo
javascript
// En producción, esto vendría de la base de datos
// y las contraseñas estarían hasheadas con bcrypt
const USUARIOS = [
{ id: 1, nombre: "Ana García", email: "ana@demo.com", password: "1234", rol: "admin" },
{ id: 2, nombre: "Pedro López", email: "pedro@demo.com", password: "1234", rol: "user" },
];
export { USUARIOS };Middleware de autenticación
javascript
// middlewares/auth.js
// Verifica que el usuario esté autenticado
export function requireAuth(req, res, next) {
if (!req.session.usuario) {
return res.status(401).json({
ok: false,
error: "No autenticado. Inicia sesión en POST /auth/login",
});
}
next();
}
// Verifica un rol específico
export function requireRole(rol) {
return (req, res, next) => {
if (!req.session.usuario) {
return res.status(401).json({ ok: false, error: "No autenticado" });
}
if (req.session.usuario.rol !== rol) {
return res.status(403).json({
ok: false,
error: `Acceso denegado. Se requiere rol: ${rol}`,
});
}
next();
};
}Rutas de autenticación
javascript
// routes/auth.js
import { Router } from "express";
import { requireAuth } from "../middlewares/auth.js";
import { USUARIOS } from "../data/usuarios.js"; // fuente de datos única
const router = Router();
// POST /auth/login
router.post("/login", (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ ok: false, error: "Email y password requeridos" });
}
const usuario = USUARIOS.find(
(u) => u.email === email && u.password === password
// ⚠ Demo: en producción usar bcrypt.compare(password, u.passwordHash)
);
if (!usuario) {
return res.status(401).json({ ok: false, error: "Credenciales incorrectas" });
}
// Buena práctica: regenerar ID de sesión al autenticar
// Previene ataques de session fixation (OWASP A07)
req.session.regenerate((err) => {
if (err) return res.status(500).json({ ok: false, error: "Error de sesión" });
// Guardar en sesión (NUNCA incluir la contraseña)
req.session.usuario = {
id: usuario.id,
nombre: usuario.nombre,
email: usuario.email,
rol: usuario.rol,
};
res.json({
ok: true,
mensaje: `Bienvenido, ${usuario.nombre}`,
usuario: req.session.usuario,
});
});
});
// POST /auth/logout
router.post("/logout", requireAuth, (req, res) => {
const nombre = req.session.usuario.nombre;
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ ok: false, error: "Error al cerrar sesión" });
}
res.clearCookie("connect.sid"); // Elimina la cookie del cliente
res.json({ ok: true, mensaje: `Hasta luego, ${nombre}` });
});
});
// GET /auth/me — Obtener usuario actual
router.get("/me", requireAuth, (req, res) => {
res.json({ ok: true, datos: req.session.usuario });
});
export default router;Rutas protegidas
javascript
// server.js
import express from "express";
import session from "express-session";
import authRouter from "./routes/auth.js";
import { requireAuth, requireRole } from "./middlewares/auth.js";
const app = express();
app.use(express.json());
app.use(session({ /* config */ }));
// Rutas públicas
app.use("/auth", authRouter);
// Rutas protegidas
app.get("/api/perfil", requireAuth, (req, res) => {
res.json({ ok: true, datos: req.session.usuario });
});
// Solo admins
app.get("/api/admin/usuarios", requireAuth, requireRole("admin"), (req, res) => {
res.json({ ok: true, datos: [{ id: 1, nombre: "Ana" }] });
});
app.listen(3000);Probando el flujo completo
bash
# 1. Login
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"ana@demo.com","password":"1234"}' \
-c cookies.txt # Guarda las cookies
# 2. Acceder a ruta protegida (enviando la cookie)
curl http://localhost:3000/api/perfil -b cookies.txt
# 3. Acceder como admin
curl http://localhost:3000/api/admin/usuarios -b cookies.txt
# 4. Logout
curl -X POST http://localhost:3000/auth/logout -b cookies.txt
# 5. Intentar acceder después del logout → 401
curl http://localhost:3000/api/perfil -b cookies.txtSesiones vs JWT
| Aspecto | Sesiones | JWT |
|---|---|---|
| Almacenamiento | Servidor (BD/Redis) | Cliente (localStorage/cookie) |
| Escalabilidad | Requiere sesión compartida | Stateless, escala fácil |
| Revocación | Inmediata (borrar sesión) | Compleja (lista negra) |
| Tamaño | Solo ID en cookie | Token completo en cada request |
| Uso ideal | Apps tradicionales, monolitos | APIs, microservicios, SPAs |