Cookies vs sesiones — JWT en cookie o session store
Una vez que el servidor autenticó al usuario, hace falta que las siguientes requests “recuerden” quién es — hay dos formas principales de resolver esto, con trade-offs distintos.
Opción 1: JWT en una cookie httpOnly
Sección titulada «Opción 1: JWT en una cookie httpOnly»En vez de devolver el JWT en el body de la respuesta (donde JavaScript del cliente lo tendría que guardar en localStorage), se manda como cookie:
pnpm add cookie-parserbun add cookie-parsernpm install cookie-parserimport cookieParser from "cookie-parser"
app.use(cookieParser())
app.post("/login", async (req, res) => { // ... verificar credenciales ... const token = firmarToken({ sub: usuario.id, rol: usuario.rol })
res.cookie("token", token, { httpOnly: true, // JavaScript del navegador NO puede leer esta cookie (protege contra XSS) secure: true, // solo se manda por HTTPS sameSite: "lax", // limita cuándo se manda en requests cross-site (protege contra CSRF) maxAge: 60 * 60 * 1000 // 1 hora, en ms })
res.json({ ok: true })})// Leer el token en requests siguientesapp.get("/perfil", (req, res) => { const token = req.cookies.token const payload = verificarToken(token) res.json({ userId: payload.sub })})httpOnly: true es la razón principal para preferir cookie sobre localStorage: un JWT en localStorage es legible por cualquier script que corra en la página (incluyendo uno inyectado por un XSS) — una cookie httpOnly no, el navegador la manda automáticamente pero JavaScript no puede tocarla.
Opción 2: sesión tradicional (server-side)
Sección titulada «Opción 2: sesión tradicional (server-side)»En vez de un JWT autocontenido, la cookie solo guarda un id de sesión random — los datos reales (quién es el usuario) viven en el servidor (memoria, Redis, base de datos).
pnpm add express-sessionbun add express-sessionnpm install express-sessionimport session from "express-session"
app.use( session({ secret: process.env.SESSION_SECRET!, resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: true, maxAge: 60 * 60 * 1000 } }))
app.post("/login", async (req, res) => { // ... verificar credenciales ... req.session.userId = usuario.id // Express guarda esto server-side, la cookie solo tiene el id de sesión res.json({ ok: true })})
app.get("/perfil", (req, res) => { if (!req.session.userId) return res.status(401).json({ error: "No autenticado" }) res.json({ userId: req.session.userId })})La diferencia real: revocación
Sección titulada «La diferencia real: revocación»| JWT (autocontenido) | Sesión server-side | |
|---|---|---|
| Verificar | Sin tocar la base — solo la firma | Consulta al store de sesiones (Redis/DB) en cada request |
| Cerrar sesión antes de que expire | No es directo (ver nota en JWT) | Trivial — borrar esa sesión del store |
| Escala horizontal | Sin estado compartido — cualquier instancia del servidor puede verificarlo | Necesita un store compartido (Redis) entre instancias |
No hay una respuesta universalmente “mejor” — JWT gana en no depender de un store centralizado; sesión server-side gana en poder revocar acceso inmediatamente (crítico para “cerrar sesión en todos los dispositivos”, o banear una cuenta al instante).
Comparación de modelos
Sección titulada «Comparación de modelos»| Cookie option | Qué hace |
|---|---|
httpOnly: true | JavaScript del navegador no puede leer la cookie (protege contra XSS) |
secure: true | Solo se manda por HTTPS |
sameSite: 'lax' | 'strict' | Limita cuándo se manda en requests cross-site (protege contra CSRF) |
maxAge | Cuánto dura antes de expirar, en ms |
Elegir transporte y revocación
Sección titulada «Elegir transporte y revocación»sameSite: 'strict'bloquea incluso navegación normal desde un link externo (el usuario llega “deslogueado” la primera vez);'lax'(el default recomendado) permite eso pero sigue bloqueando requests cross-site iniciadas por scripts o forms — el balance correcto para la mayoría de los casos.- En aplicaciones full-stack del mismo origen, Better Auth y Auth.js pueden implementar estas decisiones con opciones seguras configurables. Aun así, conviene comprender el modelo para revisar sus cookies y expiración.