La elección, en cristiano
Tanto JWT como las cookies de sesión hacen el mismo trabajo: decirle a tu backend quién está haciendo esta petición. Lo hacen de formas opuestas.
Una cookie de sesión lleva un id aleatorio. El id por sí solo no significa nada. El backend tiene una base de datos que mapea “id 8af3…” con “esto es María, logueada hace 12 minutos, admin”. En cada petición el backend busca el id, saca al usuario, y sigue.
Un JWT lleva la información de verdad, firmada por el backend. El token dice “soy María, admin, válido hasta las 9 de la noche”. El backend lee el token, verifica la firma, y sigue. Sin búsqueda en base de datos.
Los dos funcionan. Tienen tradeoffs muy distintos.
De un vistazo
| Aspecto | JWT | Cookie de sesión | Notas |
|---|---|---|---|
| Almacenamiento | Token autocontenido | Id aleatorio, almacén en servidor | JWT lleva claims, la cookie es un puntero |
| Búsqueda en servidor | No | Sí, en cada petición | La búsqueda suele estar cacheada |
| Revocación | Difícil, requiere lista negra | Fácil, borras la fila | La diferencia práctica más grande |
| Tamaño | 300 a 800 bytes | 32 a 64 bytes | Suma a gran escala |
| Mejor para | Microservicios, auth federada | App única, backend único | Elige por topología |
| Modo de fallo | Token robado vale hasta caducar | La sesión se invalida al instante | Afecta a la respuesta a incidentes |
El caso a favor de las cookies de sesión
La gran ventaja es la revocación. Si quieres cerrar la sesión de un usuario, echar una sesión, o invalidar todos los tokens después de un reset de contraseña, borras la fila en la base de datos. Hecho. La siguiente petición llega con el id viejo, la búsqueda falla, el usuario está fuera.
Los JWTs no hacen esto bien. Un JWT vale hasta que caduca, punto. Si lo quieres matar antes, necesitas una lista aparte de “tokens revocados”, que es exactamente la búsqueda en base de datos que estabas evitando.
La otra ventaja es el tamaño. Un id de sesión son 32 a 64 bytes. Un JWT con unos pocos claims son 300 a 800 bytes. Si tienes un millón de usuarios entrando en tu sitio cada día, esa diferencia se convierte en ancho de banda y coste reales. Las cookies de sesión ganan en bytes.
El precio es la búsqueda en base de datos en cada petición. Para la mayoría de apps, esa búsqueda está cacheada y añade medio milisegundo. Es un coste real, pero no suele ser uno significativo.
El caso a favor de JWT
La gran ventaja es la escalabilidad. El backend no tiene que recordar sesiones, así que añadir más nodos backend es barato. Cualquier nodo que reciba una petición puede verificar el JWT por su cuenta. No hay un almacén de sesiones compartido al que consultar.
Esto importa cuando tienes muchos backends, sistemas federados, o microservicios que necesitan saber quién es el usuario. Emitir un JWT una vez y pasarlo por diez servicios es mucho más simple que cada servicio pegándole a la misma base de datos de sesiones.
El precio es todo lo que acabamos de comentar. La revocación es difícil, el token es más grande, y cualquier información sensible que metas en el token puede ser leída por quien lo tenga (el contenido está codificado, no cifrado, échale un vistazo con el decodificador JWT y lo verás). Además tienes que manejar la rotación: tokens de acceso de vida corta con refresh tokens, lo cual añade una complejidad que sorprende.
Cómo es la superficie de ataque
Para cookies de sesión: cookie robada igual a sesión robada. Las defensas son la higiene estándar de cookies (HttpOnly, Secure, SameSite=Lax), tokens CSRF para peticiones que cambian estado, y un almacén de sesiones que permita revocar rápido.
Para JWT: token robado igual a sesión robada, hasta que caduque. Las defensas son una expiración corta (15 minutos es común), refresh tokens que sí se pueden revocar, y nunca poner JWTs en localStorage donde cualquier script los pueda agarrar. Usa cookies como transporte aunque uses JWT como contenido.
Los patrones de ataque se solapan. Un XSS roba cualquiera de los dos. Un CSRF afecta a cualquiera de los dos si usas cookies como transporte. Una brecha del servidor expone cualquiera de los dos (los ids de sesión en la base de datos, o la clave de firma del JWT).
Un árbol de decisión práctico
Usa cookies de sesión cuando:
- Tienes un único backend o un cluster pequeño.
- Necesitas cerrar sesiones al instante.
- Te importa el ancho de banda mínimo.
- Quieres el código más simple posible.
Usa JWT cuando:
- Tienes muchos backends, microservicios, o identidad federada.
- Necesitas verificación sin estado (un nodo puede verificar sin base de datos).
- Aceptas que la revocación se retrasa según la expiración del token de acceso.
- Vas a manejar bien refresh tokens, rotación, y expiración corta.
Casi todo el que lea esto debería ir por defecto a cookies de sesión. Los JWTs resuelven un problema real en escala, pero la mayoría de apps no llegan nunca a esa escala. Tirar de JWT primero es un error de ingeniería común que añade complejidad por beneficios que no necesitas.
Cuando los mezcles, hazlo bien
Muchos sistemas en producción usan los dos. Un JWT corto para la puerta de entrada (sin búsqueda en base, rápido), y una sesión larga para el refresh y la revocación. El JWT lleva la información del usuario que el frontend necesita. La sesión controla cuándo se puede reemitir el JWT.
Cuando depures este tipo de configuración, el decodificador JWT, el codificador Base64, y el convertidor de timestamps corren en tu navegador, sin subida, sin servidor de por medio. El token no sale de tu pestaña mientras confirmas qué se está rompiendo.
Elige la herramienta más simple que resuelva tu problema real. Cookies de sesión para casi todo, JWT para los momentos en los que de verdad necesitas estado nulo. Mezclarlos está bien cuando entiendes por qué cada uno está ahí.