Saltar al contenido principal

JWT vs tokens de sesión

Las sesiones guardan la verdad del lado del servidor: un token opaco de 16–32 bytes indexa un almacén que controlas, así que revocar es instantáneo y barato. Los JWT llevan la verdad dentro del propio token —firmados, sin estado, verificables en cualquier sitio—, pero revocar significa esperar a que expiren. Usa sesiones para inicios de sesión en una sola aplicación; JWT cuando muchos servicios tienen que verificar la identidad sin un almacén compartido.

Solo local

JWT vs tokens de sesión: explicación

Ambos diseños resuelven el mismo problema: las peticiones HTTP llegan sin memoria, así que cada petición necesita algo que diga «esta es la misma persona que inició sesión». Una sesión clásica le entrega al cliente un token opaco —una cadena aleatoria que apunta a estado del lado del servidor—. Un JWT (RFC 7519) le entrega al cliente el propio estado: un paquete firmado de claims que el servidor verifica sin consultar nada. Uno guarda la verdad en el servidor; el otro la pone ante notario y la manda con la petición.

Esa diferencia —dónde vive la verdad— se propaga a cada contrapartida de esta comparación. Un JWT se verifica con una comprobación de firma, así que cualquier servicio que tenga la clave puede autenticar una petición sin un almacén compartido; el precio es que el token sigue válido hasta su claim exp, de modo que «cierra la sesión ahora» y «bloquea a este usuario ahora» no tienen una implementación natural. Un token de sesión no vale nada sin su almacén, así que revocar es un borrado, pero cada servicio que verifica tiene que poder llegar a ese almacén.

Ningún lado está ganando, y los sistemas maduros ejecutan ambos de forma rutinaria: cookies de sesión opacas para la aplicación de cara a las personas y JWT de vida corta entre servicios. Las secciones siguientes comparan revocación, almacenamiento, tamaño y superficie de ataque; el decodificador enlazado a lo largo de la página te permite inspeccionar tokens reales sin subir nada.

Antes de fiarte de cualquier token en producción, despliega uno con el Decodificador de JWT — decodifica el header y el payload localmente y nunca pide una clave.

Para una anatomía segmento a segmento, la Guía de la estructura de un JWT recorre header, payload y firma con ejemplos reales.

Dos formas, dos filosofías

Una sesión de servidor es un puntero. El token —de 16 a 32 bytes aleatorios, normalmente establecido como cookie— no significa nada en sí mismo; todo el significado vive en el almacén: Redis, una base de datos, incluso la memoria del proceso. Esa indirección es la ventaja. El registro puede contener cualquier cosa, cambiar cualquier cosa o desaparecer, y los clientes jamás se enteran, porque solo sostienen un puntero.

Un JWT es una declaración. Tres segmentos base64url —header, payload, firma— portan los claims: sub para el usuario, iss para el emisor, exp para la expiración, más los claims personalizados. La firma (HMAC con un secreto compartido, o RSA/ECDSA con un par de claves) demuestra que la declaración está intacta y viene de alguien con la clave. Verificar no requiere consultas; fiarse solo requiere la clave.

Revocación: la contrapartida que decide la mayoría de los proyectos

Con sesiones, «cerrar sesión» y «bloquear usuario» son operaciones sobre el almacén: borras el registro y la siguiente petición falla. Con JWT, el token sigue siendo criptográficamente válido hasta exp, sin importar lo que el servidor emisor desee ahora: la gracia de no tener estado es que la verificación nunca devuelve la llamada. Las mitigaciones son bien conocidas y todas reintroducen estado: expiración corta con rotación de refresh tokens, listas de denegación por jti o un claim de versión que se incrementa al cerrar sesión. Cada una reconstruye una porción del almacén del que renunciaste.

Enmarcado con honestidad: las sesiones hacen la revocación gratis pero cobran una consulta al almacén en cada petición; los JWT la convierten en una decisión de diseño que pagas a la hora de cerrar sesión. Si «cerrar sesión en todas partes, inmediatamente» es un requisito, las sesiones o una rotación muy bien gestionada están en tu futuro.

Almacenamiento y superficie de ataque: cookies, localStorage, XSS y CSRF

Dónde vive el token importa tanto como qué es. Un JWT aparcado en localStorage es legible por todos los scripts de la página, así que un único fallo de XSS exfiltra una credencial bearer válida hasta la expiración, sin forma de revocarla del lado del servidor. Un token opaco en una cookie HttpOnly, Secure y SameSite es invisible para JavaScript, lo que limita el radio de impacto de un XSS; el precio es la exposición a CSRF —el navegador adjunta la cookie automáticamente—, mitigada con SameSite y tokens CSRF.

Los JWT también pueden viajar en cookies (y a menudo deberían), con las mismas advertencias de CSRF; una cabecera Authorization esquiva el CSRF pero deja el token otra vez al alcance de JavaScript. Ninguna disposición de almacenamiento es segura contra XSS: solo hay disposiciones que limitan lo que el atacante puede robar.

El tamaño, y lo que un JWT revela de verdad

Un JWT de inicio de sesión típico mide 800 bytes o más con unos pocos claims, y cada petición lo repite, mientras que las cookies se quedan alrededor de 4 KB por dominio como tope. Un token de sesión opaco se mantiene en 16–32 bytes sin importar nada. A escala, la diferencia se nota en cabeceras, proxies y logs.

El payload es base64url —codificación, no cifrado—, así que cualquiera que tenga el token puede leer cada claim, datos personales y permisos incluidos. JWE existe para tokens cifrados y casi nunca se usa. Pega un token real en el decodificador de abajo: el header y el payload se despliegan en tu navegador, la firma se muestra pero nunca se verifica, porque verificar necesita la clave secreta, y la herramienta nunca la pide.

AspectoSesión de servidorJWT (RFC 7519)
Dónde vive la verdadAlmacén del lado del servidorDentro del token firmado
RevocaciónInstantánea — borra el registroEn exp, o añadiendo estado
Tamaño del token16–32 bytes opacos~800 B o más con claims típicos
Coste de la verificaciónConsulta al almacén por peticiónComprobación de firma, sin consultas
Autenticación multi-servicioRequiere un almacén de sesiones compartidoCualquier servicio que tenga la clave
ExpiraciónTTL del lado del servidor, modificableClaim exp, fijo al emitirse
Almacenamiento en el navegadorCookie HttpOnly + SameSiteCookie, o localStorage (riesgo de XSS)

Cuándo elegir cada uno

Elige sesiones cuando una sola aplicación gestiona el inicio de sesión: sitios renderizados en servidor, paneles de administración, cualquier cosa donde el cierre de sesión instantáneo y la gestión de dispositivos valgan una consulta a Redis. Elige JWT cuando la verificación tiene que ocurrir en muchos sitios —microservicios, apps móviles, servicios entre equipos— y un almacén de sesiones compartido para todos ellos es el coste más pesado.

La recomendación se invierte con la misma claridad. En cuanto construyes una lista de denegación o un servicio de rotación para hacer revocables los JWT, pregúntate si un almacén de sesiones se ha colado de contrabando; en cuanto un almacén de sesiones estrangula a una docena de servicios, pregúntate si JWT de vida corta entre ellos no serían más ligeros. Los híbridos —una cookie opaca para el navegador, JWT de servicio a servicio— no son un compromiso: son el estado final normal.

Preguntas frecuentes

¿Puedo decodificar un JWT aquí sin subirlo ni enviarlo a ningún sitio?

Sí. El decodificador se ejecuta por completo en tu navegador: el header y el payload son base64url en claro, así que se despliegan localmente sin que ninguna petición salga de la pestaña — la Content-Security-Policy del sitio bloquea las llamadas salientes. La firma se muestra para inspeccionarla, pero nunca se verifica: verificar requiere la clave de firma, y la herramienta nunca la pide.

¿Un JWT está cifrado?

No. RFC 7519 define un token firmado: el payload es base64url —legible por cualquiera que lo tenga— y la firma solo demuestra quién lo emitió y que no fue alterado. El cifrado es un perfil aparte (JWE) que los sistemas reales rara vez usan. Trata cada claim como público: identificadores y roles están bien; secretos y datos personales, no.

¿Cómo revoco un JWT de inmediato?

No puedes con el token a secas: ese es el precio de no tener estado. Los sistemas reales reintroducen estado: ventanas exp cortas con rotación de refresh tokens para que los tokens robados mueran rápido, una lista de denegación indexada por el claim jti, o un claim de versión de token que se incrementa al cerrar sesión. Las tres son un almacén de sesiones con pasos extra.

¿Las cookies HttpOnly son más seguras que localStorage para los tokens?

Contra XSS, sí. localStorage es legible por todos los scripts de la página, así que una sola inyección roba un token bearer válido hasta la expiración; una cookie HttpOnly es invisible para JavaScript, así que la misma inyección no puede llevárselo. El precio de la cookie es el CSRF, gestionado con SameSite y tokens CSRF. HttpOnly no derrota al XSS: solo quita el token del botín.

¿Por qué los microservicios prefieren JWT a las sesiones?

Porque la verificación se vuelve local. Con RS256, cada servicio tiene la clave pública y valida tokens sin llamar a un almacén de sesiones compartido; un servicio nuevo arranca con una clave, no con una integración. Las sesiones opacas ponen a cada servicio a merced de la disponibilidad de un único almacén. Esa independencia es lo que compra la ausencia de estado; la revocación es la factura.

¿Hasta qué tamaño puede crecer un JWT antes de ser un problema?

Vigila primero las cookies: los navegadores las limitan alrededor de 4 KB por dominio, y un JWT cargado de claims puede acercarse a ese límite y romper peticiones directamente. Incluso por debajo del límite, un token de ~800 bytes viaja en cada petición, mientras que un token de sesión opaco se queda en 16–32 bytes sin remedio. Mantén los claims magros: identidad y expiración, no datos de perfil.

¿Dónde puedo leer las normas citadas aquí?