Autenticación basada en sesiones vs. basada en tokens (JWT) — ¿cuáles son las diferencias reales?
Las sesiones guardan el estado en el servidor y se pueden revocar al instante; los JWT guardan el estado en el cliente y escalan de maravilla hasta que necesitas revocar uno.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Autenticación basada en sesiones vs. basada en tokens (JWT) — ¿cuáles son las diferencias reales?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Dos filosofías distintas
La autenticación basada en sesiones y la basada en tokens resuelven el mismo problema — "recordar que este request pertenece a un usuario que ya inició sesión" — con arquitecturas opuestas. Una sesión es con estado (stateful): el servidor crea un registro en el momento del login, y cada request posterior es simplemente una búsqueda contra ese registro. Un JWT es sin estado (stateless): el servidor emite una credencial firmada y autocontenida y luego se olvida por completo de ella; cada request posterior se verifica comprobando una firma, no buscando nada. Esa única decisión arquitectónica se propaga a casi todas las diferencias que siguen.
#Autenticación basada en sesiones, de punta a punta
// server.js — autenticación basada en sesiones con express-session + Redisconst express = require('express');const session = require('express-session');const RedisStore = require('connect-redis').default;const { createClient } = require('redis');const bcrypt = require('bcrypt');const app = express();app.use(express.json());const redisClient = createClient({ url: process.env.REDIS_URL });redisClient.connect();app.use(session({store: new RedisStore({ client: redisClient, prefix: 'sess:' }),secret: process.env.SESSION_SECRET,resave: false,saveUninitialized: false,cookie: {httpOnly: true,secure: true, // solo HTTPSsameSite: 'lax',maxAge: 1000 * 60 * 30, // 30 minutos},}));app.post('/login', async (req, res) => {const { email, password } = req.body;const user = await findUserByEmail(email);if (!user || !(await bcrypt.compare(password, user.passwordHash))) {return res.status(401).json({ error: 'Credenciales inválidas' });}req.session.userId = user.id; // el servidor ahora es dueño de esta sesiónres.json({ ok: true });});app.post('/logout', (req, res) => {req.session.destroy(() => res.json({ ok: true })); // revocación real e instantánea});app.get('/me', requireSession, async (req, res) => {const user = await findUserById(req.session.userId);res.json({ id: user.id, email: user.email });});function requireSession(req, res, next) {if (!req.session.userId) return res.status(401).json({ error: 'No autenticado' });next();}
El cliente solo guarda un valor de cookie opaco — no tiene ni idea de qué hay dentro de la sesión, y no lo necesita. Todos los datos interesantes (ID de usuario, roles, contenido del carrito, lo que sea) viven en el servidor, indexados por ese ID opaco.
#Autenticación basada en tokens (JWT), de punta a punta
// server.js — autenticación basada en un JWT firmadoconst express = require('express');const jwt = require('jsonwebtoken');const bcrypt = require('bcrypt');const app = express();app.use(express.json());const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;app.post('/login', async (req, res) => {const { email, password } = req.body;const user = await findUserByEmail(email);if (!user || !(await bcrypt.compare(password, user.passwordHash))) {return res.status(401).json({ error: 'Credenciales inválidas' });}const token = jwt.sign({ sub: user.id, role: user.role },ACCESS_SECRET,{ algorithm: 'HS256', expiresIn: '15m' });res.json({ token }); // el servidor lo entrega y se olvida de él});app.get('/me', requireToken, async (req, res) => {const user = await findUserById(req.auth.sub);res.json({ id: user.id, email: user.email });});function requireToken(req, res, next) {const header = req.headers.authorization || '';const [scheme, token] = header.split(' ');if (scheme !== 'Bearer' || !token) {return res.status(401).json({ error: 'Falta el bearer token' });}try {req.auth = jwt.verify(token, ACCESS_SECRET, { algorithms: ['HS256'] });next();} catch {return res.status(401).json({ error: 'Token inválido o expirado' });}}// No existe un /logout que realmente revoque algo — el token sigue siendo// válido hasta que expira, sin importar lo que haga el servidor después.
#Las diferencias, lado a lado
| Dimensión | Basada en sesiones | Basada en tokens (JWT) |
|---|---|---|
| Dónde vive el estado | Servidor (Redis/BD) + un ID de cookie opaco en el cliente | Enteramente en el cliente; el servidor solo guarda la clave de firma |
| Revocación | Instantánea — se borra el registro de sesión | Difícil por diseño — válido hasta exp salvo que construyas una lista de bloqueo |
| Escalado horizontal | Necesita un almacén de sesiones compartido o sesiones "pegajosas" | Cualquier servidor con la clave puede verificar de forma independiente |
| Transporte habitual | Cookie HttpOnly, enviada automáticamente por el navegador | Header Authorization, adjuntado manualmente por el código del cliente |
| Exposición a CSRF | Mayor — las cookies viajan solas, hace falta token CSRF o SameSite | Menor — nada se adjunta automáticamente |
| Exposición a XSS | Menor, si la cookie es HttpOnly | Mayor si se guarda en localStorage/sessionStorage — cualquier script inyectado puede leerlo y exfiltrarlo |
| Overhead de payload por request | Pequeño (un ID de cookie) | Mayor — los claims viajan en cada request |
| Móvil / cross-domain / microservicios | Incómodo — los cookie jars y CORS lo complican | Encaja de forma natural — un valor de header opaco, sin acoplamiento de dominio |
| Mejor opción por defecto para | Apps web renderizadas en servidor, mismo origen | APIs públicas, clientes móviles, autenticación servicio a servicio |
Ningún modelo es "más seguro" en abstracto — las cookies de sesión cambian riesgo de CSRF por revocación fácil, los JWT cambian dificultad de revocación por ausencia de estado y portabilidad. La elección correcta depende de lo que estés construyendo: una app web de un solo origen con un backend tradicional se inclina hacia sesiones; una API pública que sirve apps móviles nativas e integraciones de terceros se inclina hacia tokens; la mayoría de los sistemas reales, como dice el tip de arriba, terminan haciendo un poco de ambos.
🧠 Check your understanding
0/3 · 0/3 answered1. ¿Por qué es difícil lograr un cierre de sesión forzado e instantáneo ('mata esta sesión ahora mismo') con un diseño puramente basado en JWT?
2. ¿Qué factor empuja a un sistema hacia la autenticación basada en tokens en lugar de sesiones?
3. En el patrón híbrido descrito en la lección, ¿para qué se usa típicamente el refresh token?