JWT vs Sesión: ¿qué mecanismo de autenticación elegir?
Autenticar a un usuario significa saber en cada petición quién es. Se enfrentan dos grandes familias: las sesiones de servidor, donde el servidor mantiene el registro del usuario conectado, y los JSON Web Tokens (JWT), donde la identidad viaja en un token firmado que porta el cliente. La elección influye en la seguridad, en la capacidad de escalar y en la facilidad para desconectar a un usuario. Veamos cómo decidir según tu contexto.
Las sesiones de servidor (stateful)
Con una sesión, el servidor crea un identificador de sesión al iniciar sesión, almacena los datos asociados (identidad, roles, carrito) del lado del servidor (memoria, Redis, base de datos) y devuelve al navegador una cookie que contiene solo ese identificador. En cada petición, el servidor lee la cookie, recupera la sesión y sabe quién habla.
- Estado del lado del servidor: la fuente de verdad permanece en el servidor, el cliente solo porta una referencia opaca.
- Revocación inmediata: eliminar la sesión del lado del servidor desconecta al usuario al instante.
- Cookie: transmitida automáticamente por el navegador, idealmente en
HttpOnly,SecureySameSite.
Los JSON Web Tokens (stateless)
Un JWT es un token autoportante compuesto por tres partes codificadas en base64url y separadas por puntos: un header, un payload (los claims, por ejemplo el identificador de usuario y la expiración) y una firma. El servidor firma el token al iniciar sesión; después le basta con verificar la firma para confiar en el contenido, sin almacenar nada.
- Sin estado: toda la información necesaria está en el token, el servidor no necesita memoria compartida.
- Verificable en cualquier sitio: cualquier servicio que conozca la clave puede validar el token, práctico para arquitecturas distribuidas y el SSO.
- Herramientas: puedes inspeccionar un token con nuestro decodificador JWT, comprobar su firma con el verificador JWT o forjar uno con el generador JWT.
Tabla comparativa
| Criterio | Sesión de servidor | JWT |
|---|---|---|
| Estado | Stateful (almacenado en servidor) | Stateless (portado por el cliente) |
| Almacenamiento en servidor | Requerido (Redis, base de datos) | Ninguno |
| Revocación | Inmediata | Difícil antes de la expiración |
| Escalabilidad horizontal | Requiere store compartido | Nativa |
| Tamaño transmitido | Pequeño (un identificador) | Mayor (claims firmados) |
| Entre dominios / SSO | Engorroso | Adecuado |
| Superficie XSS | Baja si cookie HttpOnly | Alta si se almacena en localStorage |
Seguridad: XSS, CSRF y revocación
Ambos enfoques son seguros si se implementan bien, pero sus riesgos difieren.
- XSS: una cookie de sesión
HttpOnlyes inaccesible para JavaScript, por lo que está protegida del robo por inyección. Un JWT almacenado enlocalStoragees, en cambio, legible por cualquier script, lo que lo convierte en un blanco preferente. Almacenar el JWT en una cookieHttpOnlyanula esta ventaja del JWT pero reintroduce el riesgo de CSRF. - CSRF: las cookies se envían automáticamente, por lo que son vulnerables al CSRF sin protección (atributo
SameSite, token anti-CSRF). Un JWT enviado manualmente en la cabeceraAuthorizationno se ve afectado. - Revocación: es el punto débil del JWT. Como es autoportante, no se puede invalidar antes de su expiración sin reintroducir un estado de servidor (lista de revocación, blacklist). Una sesión se elimina al instante.
Escalabilidad y arquitectura
En un solo servidor, las sesiones son triviales. En cuanto repartes la carga entre varias instancias, cada instancia debe acceder a las sesiones: hace falta un store compartido (Redis) o sticky sessions. El JWT brilla aquí, porque cualquier instancia valida el token sin llamada de red ni almacenamiento común.
- Microservicios: un JWT propaga la identidad de un servicio a otro sin una base central.
- APIs públicas y móviles: el JWT evita la gestión de cookies del lado del cliente nativo.
- Monolito clásico: la sesión sigue siendo más sencilla y más segura por defecto.
Cuándo elegir uno u otro
Elegir las sesiones cuando
- Desarrollas una aplicación web clásica con renderizado en servidor
- La revocación inmediata es crítica (banca, salud, back-office)
- Quieres la solución más segura por defecto, con la menor cantidad de trampas
- Tu infraestructura soporta un store de sesiones compartido sin dolor
Elegir el JWT cuando
- Expones una API consumida por SPAs, móviles o terceros
- Tienes una arquitectura de microservicios o SSO entre dominios
- Necesitas escalar horizontalmente sin store compartido
- Aceptas gestionar la expiración corta y la renovación de los tokens
Recomendación
Para la mayoría de las aplicaciones web, las sesiones de servidor siguen siendo la opción más segura y sencilla: revocación inmediata, cookie HttpOnly y cero gestión de tokens del lado del cliente. Reserva el JWT para los casos en los que su ausencia de estado aporta un valor real: API stateless, móvil, microservicios, SSO.
Si optas por el JWT, mantén una vida útil corta (unos minutos) combinada con un refresh token almacenado en una cookie HttpOnly, y prevé una lista de revocación para los casos sensibles. Así combinas lo mejor de ambos mundos.
Preguntas frecuentes
¿Está cifrado un JWT?
No, por defecto un JWT solo está firmado, no cifrado. Su payload está codificado en base64url y es legible para cualquiera que lo intercepte. Nunca coloques datos sensibles en claro dentro de un JWT. Para cifrar el contenido, hay que recurrir a JWE (JSON Web Encryption).
¿Dónde almacenar un JWT del lado del cliente?
Lo más seguro es una cookie HttpOnly, Secure y SameSite, que protege del robo por XSS. El localStorage es más sencillo pero expone el token a cualquier script malicioso. Evítalo para tokens con privilegios altos.
¿Cómo desconectar a un usuario con un JWT?
Como el token es autoportante, la desconexión real exige o bien esperar a su expiración, o bien mantener una lista de revocación del lado del servidor. Por eso se usan vidas útiles cortas y un refresh token que sí se puede revocar.
¿Se pueden combinar sesiones y JWT?
Sí, es una práctica habitual: un access token JWT de vida corta para las llamadas de API, y un refresh token gestionado como una sesión (almacenado y revocable del lado del servidor) para renovar el access token.