Gestión de sesiones: cookies, tokens, timeouts y revocación

Cómo se construyen y se defienden las sesiones web: los atributos de las cookies (HttpOnly, Secure, SameSite, Domain, Path), los tokens de sesión en el servidor frente a los JWT y sus compromisos, los tiempos de inactividad y absolutos, la reautenticación forzada, cómo revocar de verdad una sesión y los controles contra CSRF, XSS y el secuestro de sesión.

“Cerrar sesión no me cierra la sesión”

El informe de error llega a la mesa de David con un título corto: “Cerrar sesión no me cierra la sesión”. Un cliente cerró sesión en la app web desde un ordenador compartido de una biblioteca y, por curiosidad, pulsó el botón de atrás del navegador y volvió a estar dentro de su cuenta. David lo reproduce en cinco minutos. El botón de cerrar sesión borra la cookie del navegador, pero la sesión a la que apuntaba sigue viva en el servidor, y además el token de sesión es un JWT que vale durante ocho horas pase lo que pase. Cualquiera que haya copiado esa cookie, o que se siente en ese ordenador antes de que se limpie la caché, puede seguir como si fuera el cliente.


La autenticación es un momento, el instante en que alguien demuestra quién es. HTTP, en cambio, no guarda estado. Sin ayuda, el servidor olvidaría esa prueba en la siguiente petición y pediría la contraseña en cada clic. La sesión es lo que recuerda que este navegador ya se autenticó, para que la persona lo demuestre una vez y siga reconocida. Eso convierte la sesión en uno de los activos más atacados de cualquier aplicación: un ID de sesión válido lleva consigo una identidad autenticada, y quien lo tiene es el usuario, sin necesidad de contraseña. El artículo de SSO no dejaba de tropezar con esto, con la sesión maestra, las sesiones de aplicación independientes y lo difícil que es cerrarlas. Este artículo abre esa caja: cómo se construyen las sesiones con cookies y tokens, cuánto deben durar, cómo se terminan y cómo se defienden de los ataques que van a por ellas.


Es un artículo técnico, con atributos, cabeceras y compromisos concretos. La referencia de cabecera es la OWASP Session Management Cheat Sheet, que combina bien con todo lo que sigue.


Las cookies y sus atributos

En las apps de navegador, la sesión viaja casi siempre en una cookie, un valor pequeño que el servidor fija y el navegador devuelve automáticamente en las peticiones siguientes. El valor en sí debería ser un identificador opaco y de alta entropía. Casi toda la seguridad está en los atributos que lo acompañan, y olvidar uno es darle una palanca al atacante.

Set-Cookie: sid=9f2a...e71; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800
AtributoQué hacePor qué importa
HttpOnlyOculta la cookie a JavaScript (document.cookie)Un ataque XSS no puede leer el ID de sesión; el atributo más importante
SecureEnvía la cookie solo por HTTPSImpide interceptar el ID de sesión en claro
SameSiteDecide si acompaña a las peticiones entre sitiosLa principal defensa del navegador contra el CSRF
DomainQué hosts reciben la cookieUn dominio demasiado amplio la filtra a subdominios que no deberían tenerla
PathQué rutas reciben la cookieAcota la cookie; un control menor de defensa en profundidad
Max-Age / ExpiresCuánto vive la cookieOmítelo para una cookie de sesión que muere con el navegador; fíjalo para que persista

SameSite merece una mirada más atenta, porque es tu primera línea contra el CSRF. Con Strict, la cookie no se envía nunca en una petición entre sitios, ni siquiera en una navegación de primer nivel. Es el ajuste más fuerte, pero quien sigue un enlace desde un correo o desde otra web llega sin sesión, lo que puede estropear la experiencia. Con Lax, la cookie se envía en las navegaciones GET de primer nivel, como pulsar un enlace, pero no en los POST entre sitios, en iframes ni en peticiones en segundo plano. Es el valor por defecto razonable para las cookies de sesión: corta la vía clásica del CSRF y deja funcionar la navegación normal. Con None, la cookie va en todas las peticiones entre sitios y tiene que ir acompañada de Secure. Úsalo solo cuando de verdad necesites cookies entre sitios, como en algunos flujos incrustados o federados, y compénsalo con tokens anti-CSRF.


Para una cookie de sesión propia, un buen punto de partida es HttpOnly; Secure; SameSite=Lax; Path=/, un Domain estrecho y ningún Max-Age persistente salvo que quieras a propósito un “recordarme”. Afloja un atributo solo cuando un requisito concreto te obligue, y deja escrito por qué.


Tokens de sesión en el servidor frente a JWT

Detrás de la cookie hay una decisión que condiciona todo lo relativo a la duración y la revocación: ¿la sesión tiene estado y vive en el servidor, o no tiene estado y es autocontenida?


Con una sesión en el servidor, la cookie contiene un ID aleatorio y opaco que no significa nada por sí solo. Es una llave hacia un estado de sesión que el servidor guarda en memoria, en Redis o en una base de datos, y en cada petición el servidor busca el ID y lee la identidad, los roles y los metadatos asociados. El servidor es la fuente de verdad, así que puedes revocar al instante borrando el registro, guardar un estado rico que cambia con el tiempo y no perder nada si interceptan la cookie, porque es solo una cadena aleatoria. El precio es un almacén en el servidor y una consulta en cada petición, y ese almacén tiene que escalar y estar compartido entre instancias.


Un JWT usado como sesión es un token firmado y autodescriptivo que lleva dentro los claims: sujeto, roles, expiración. El servidor comprueba la firma y lee los claims sin consultar nada, y no guarda nada por su parte. No tiene estado, escala en horizontal sin almacén de sesiones y encaja de forma natural en el mundo de OAuth y OIDC. El precio es la revocación. Como el servidor no guarda nada, no hay nada que borrar, y un JWT robado sigue valiendo hasta que caduca. Los claims también pueden quedarse anticuados, de modo que un rol retirado a mitad de sesión sigue apareciendo en el token, y un token más grande viaja en cada petición.


Token en el servidorJWT
Fuente de verdadEl almacén del servidorEl propio token
RevocaciónInmediata (borrar el registro)Difícil (esperar a que caduque o usar una lista de denegación)
EscaladoNecesita un almacén compartidoSin estado, fácil
Actualidad del estadoSiempre al díaPuede quedarse anticuado hasta que se reemita
Encaja mejor enApps web sensibles que necesitan controlAPIs y microservicios que necesitan escala

El antipatrón habitual es justo lo que encontró David: un único JWT con una vida de horas o días usado como sesión entera. No se puede revocar, así que una copia robada funciona como llave maestra hasta que caduca. Si usas JWT, mantén el access token de vida corta, cuestión de minutos, y acompáñalo de un refresh token revocable respaldado por estado en el servidor, que devuelve el control que a los JWT les falta. Para las apps web de alto valor, las sesiones opacas en el servidor suelen ser sin más la opción más segura por defecto.


Duración: tiempo de inactividad y tiempo absoluto

Una sesión no debería vivir para siempre. Dos relojes independientes deciden cuánto dura, y los sistemas maduros usan los dos.


stateDiagram-v2
  accTitle: Ciclo de vida de una sesión web con tiempo de inactividad y tiempo absoluto
  accDescr: Una sesión empieza anónima. Un inicio de sesión correcto la pasa a activa. Desde activa, la inactividad la lleva a inactiva; cualquier petición la devuelve a activa y reinicia el reloj de inactividad. Llegar al tiempo de inactividad o al tiempo absoluto la pasa a terminada. El cierre de sesión o la revocación administrativa también la pasan de activa a terminada. Desde terminada, reautenticarse devuelve al usuario a activa con una sesión nueva.
  state "Anónima" as Anonima
  [*] --> Anonima
  Anonima --> Activa: inicio de sesión
  Activa --> Activa: petición reinicia el reloj
  Activa --> Inactiva: inactividad
  Inactiva --> Activa: nueva petición
  Inactiva --> Terminada: tiempo de inactividad agotado
  Activa --> Terminada: tiempo absoluto agotado
  Activa --> Terminada: cierre de sesión / revocada
  Terminada --> Activa: reautenticación (sesión nueva)
  Terminada --> [*]
El ciclo de vida de la sesión. Iniciar sesión lleva al usuario de anónimo a activo. La inactividad lo acerca al límite de inactividad; cualquier petición lo devuelve a activo. Dos cosas terminan la sesión: llegar al tiempo de inactividad o alcanzar el tiempo absoluto que limita la duración total desde el inicio de sesión. El cierre de sesión o una acción de un administrador pueden revocarla en cualquier momento. Una sesión terminada obliga a reautenticarse para empezar otra.

El tiempo de inactividad, también llamado deslizante, termina la sesión tras un periodo sin actividad y se reinicia con cada petición. Quien está trabajando no se ve interrumpido, y una sesión abierta en una máquina desatendida, como el ordenador de la biblioteca, muere sola. Los valores típicos van de unos pocos minutos en apps sensibles a decenas de minutos en las normales. El tiempo absoluto termina la sesión un tiempo fijo después del inicio de sesión, por activo que esté el usuario. Limita la duración total y obliga a reautenticarse cada cierto tiempo, así que ni una sesión en uso constante, ni una que un ladrón use sin parar, puede vivir para siempre. Los valores típicos son horas en apps web y menos en contextos privilegiados.


Úsalos juntos: un tiempo de inactividad corto caza el abandono y un límite absoluto más largo obliga a demostrar de nuevo la identidad de forma periódica. Más allá de los tiempos, fuerza a propósito la reautenticación en las acciones arriesgadas. Es la autenticación escalonada del artículo de factores aplicada a la sesión: consultar datos puede ir con la sesión existente, pero cambiar una contraseña, añadir un beneficiario o exportar registros debería pedir un factor nuevo aunque la sesión siga siendo válida.


No hay un número universal, pero el riesgo de la app marca el rango. Las apps muy sensibles, como la banca o las consolas de administración, tiran hacia tiempos de inactividad de pocas decenas de minutos y límites absolutos de unas pocas horas; las apps de productividad normales toleran más en ambos; y cuanto más sensible es la acción, más te apoyas en la autenticación escalonada y menos en un tiempo global permisivo. El error es un único tiempo generoso para todo, que se adapta al flujo de menor riesgo y deja el de mayor riesgo expuesto igual de tiempo. Elijas lo que elijas, aplícalo en el servidor. Un temporizador en el navegador que solo oculta la interfaz deja la sesión viva y reutilizable, y eso no es ningún límite. Y documenta el razonamiento, porque “¿por qué 30 minutos?” es una pregunta que los auditores hacen siempre.


Revocación: terminar una sesión ya

Los tiempos terminan las sesiones tarde o temprano. La revocación termina una sesión concreta ahora mismo: al cerrar sesión, cuando se detecta un compromiso, cuando un administrador desactiva una cuenta, cuando alguien deja la empresa. Lo difícil que sea depende por completo de la elección entre sesiones con estado y sin estado.


Las sesiones en el servidor se revocan limpiamente. Borras o marcas el registro y la siguiente petición falla la validación, que es la ventaja operativa decisiva de las sesiones opacas y la razón de que la respuesta a incidentes y las bajas las prefieran. Los JWT sin estado no tienen registro que borrar, así que necesitas otro mecanismo. Puedes mantener vidas cortas y hacer revocables los refresh tokens: el access token caduca en minutos y revocar el refresh token impide renovarlo, con lo que la ventana de exposición queda limitada a la vida del access token, aunque no se cierra del todo. Puedes mantener una lista de denegación de ID de token revocados (jti) que se comprueba en cada petición, lo que funciona pero devuelve justo la consulta que los JWT querían evitar. O puedes guardar una versión de token, un valor de “no antes de” por usuario, y subirla para invalidar de golpe todos sus tokens, algo útil para “cerrar sesión en todas partes” y tras un cambio de contraseña.


El resumen honesto es que la revocación inmediata y fiable siempre necesita algo de estado en el servidor. Las sesiones sin estado del todo cambian revocabilidad por escala, lo que está bien para APIs de bajo riesgo y es peligroso para cuentas valiosas.


Fijación de sesión y rotación del ID

Hay un ataque que merece trato aparte porque su defensa es un hábito y no un atributo. En la fijación de sesión, el atacante consigue primero un ID de sesión válido, que muchas apps dan a los visitantes anónimos, y después engaña a la víctima para que inicie sesión con ese mismo ID, mediante un enlace preparado o una cookie fijada de antemano. Como la app mantuvo el ID igual al iniciar sesión, el atacante, que ya lo conoce, comparte ahora la sesión autenticada de la víctima.


El arreglo es sencillo e innegociable: rota el identificador de sesión cada vez que cambien los privilegios y, sobre todo, justo después de un inicio de sesión correcto. Emite un ID completamente nuevo e invalida el anterior, y el atacante que lo fijó se queda con un valor muerto. Lo mismo vale para cualquier autenticación escalonada o elevación de rol. Siempre que sube el nivel de confianza de la sesión, el identificador debe cambiar, para que nada capturado a un nivel inferior pueda aprovechar la subida.


Sesiones persistentes: “recordarme” sin riesgos

Una cookie de sesión normal muere con el navegador. “Recordarme” la alarga a propósito con una cookie persistente, para que el usuario no tenga que iniciar sesión cada día. Es cómodo, y arriesgado si se hace a la ligera, porque estás guardando una credencial de larga duración en un dispositivo que no controlas.


Usa un token de “recordarme” aparte y de larga duración, distinto del token de sesión corto, en lugar de una cookie de sesión con un Max-Age enorme. Haz que sea un valor aleatorio de alta entropía guardado en el servidor como hash, para que se pueda revocar y una filtración de la base de datos no exponga tokens utilizables. Rótalo en cada uso y trata la reutilización de un token antiguo como señal de robo, porque alguien ha reutilizado un token que ya habías rotado, y entonces invalida toda la cadena. Y nunca le des a una sesión recordada el mismo poder que a una recién iniciada. Un usuario recordado puede navegar; cambiar la contraseña, los datos de pago o la configuración de seguridad sigue exigiendo autenticación escalonada. “Recordarme” devuelve comodidad, no confianza plena.


Más allá del navegador: apps móviles y APIs

No todas las sesiones son cookies. Las apps móviles y los clientes de APIs suelen llevar un token al portador, a menudo un access token de OAuth, en una cabecera Authorization: Bearer. La idea es la misma, una credencial que demuestra una sesión autenticada, pero las defensas cambian. SameSite y el CSRF pierden casi todo su papel en las APIs que solo usan tokens al portador, porque el navegador no adjunta ninguna cookie automáticamente; el cliente envía el token de forma explícita, aunque los diseños que mezclan cookies y tokens siguen necesitando cuidado con el CSRF. El almacenamiento pasa a ser el riesgo: un token en el localStorage del navegador lo puede leer cualquier XSS, y por eso las apps web sensibles suelen guardar la sesión en una cookie HttpOnly, y en el móvil los tokens van en el almacén de claves o el llavero de la plataforma, no en ficheros normales. Lo habitual son access tokens de vida corta con refresh tokens, en la línea de lo dicho sobre los JWT. Y DPoP (RFC 9449) refuerza los tokens al portador atándolos criptográficamente a la clave del cliente, de modo que un token robado no se puede reutilizar desde otro dispositivo, que es atacar de frente la debilidad de “quien lo tiene lo usa” que trata el artículo de la frontera. Viva la sesión en una cookie o en una cabecera, el trabajo es el mismo: que no se pueda leer, reutilizar ni durar más de la cuenta.


Los ataques: secuestro, XSS y CSRF

La capa de sesión tiene tres ataques característicos, y conocer los controles de cada uno es conocimiento básico del oficio.


El secuestro de sesión consiste en robar un ID de sesión válido y reutilizarlo para hacerse pasar por el usuario. Se roba interceptándolo en claro, leyendo una cookie accesible desde JavaScript, encontrándolo en URL o en logs, o cogiéndolo de una máquina compartida. Los controles son las cookies Secure y HttpOnly, TLS en todas partes, no poner nunca ID de sesión en URL, rotar el ID tras el inicio de sesión, atar las sesiones al contexto, como el dispositivo y la red aproximada, y volver a comprobar ante anomalías, y tiempos cortos que reducen la ventana de reutilización.


El cross-site scripting (XSS) inyecta un script del atacante en una página para que se ejecute en el navegador de la víctima con los privilegios del sitio. Si la cookie de sesión no es HttpOnly, el script puede leerla y sacarla directamente; aun con HttpOnly, puede aprovechar la sesión para hacer peticiones autenticadas. Los controles son la codificación de la salida según el contexto y la validación de la entrada para cortar la inyección de raíz, una Content-Security-Policy fuerte que limite qué scripts pueden ejecutarse y HttpOnly para que la cookie siga siendo ilegible. El XSS es la razón de que HttpOnly no se negocie, pero solo oculta la cookie. No arregla la inyección, que también tienes que cerrar.


La falsificación de peticiones entre sitios (CSRF) engaña al navegador de un usuario con sesión iniciada para que envíe a tu web una petición autenticada que no quería, como un formulario oculto o una etiqueta de imagen que dispara un POST que cambia algo. El navegador adjunta la cookie de sesión automáticamente, así que la petición parece legítima. Los controles son las cookies SameSite=Lax o Strict, que bloquean el envío clásico entre sitios; los tokens anti-CSRF, un valor impredecible por sesión o por petición que el servidor comprueba en cada petición que cambia algo; y comprobar Origin o Referer en los endpoints sensibles. Como SameSite tiene casos límite y huecos en navegadores antiguos, combínalo con tokens anti-CSRF en todo lo que cambie estado.


Estas defensas encajan entre sí y ninguna funciona sola. HttpOnly limita el daño del XSS pero no lo evita. SameSite frena el CSRF pero no sustituye a los tokens anti-CSRF. Los tiempos cortos reducen la ventana del secuestro pero no impiden el robo. La seguridad real de la sesión es la combinación de atributos seguros en las cookies, prevención del XSS mediante codificación y CSP, tokens anti-CSRF, rotación del ID, tiempos sensatos y revocación en el servidor. Quita cualquiera y un ataque concreto vuelve a abrirse.


Dónde vive el almacén de sesiones

Elegir sesiones en el servidor plantea una pregunta práctica que a los partidarios de los JWT les gusta sacar: ¿dónde vive el estado y escala? En un solo servidor, las sesiones en memoria son triviales. En cuanto tienes varias instancias detrás de un balanceador de carga, una sesión en la memoria de un servidor es invisible para los demás, y una petición que va a parar a otro parece no tener sesión.


Hay tres respuestas habituales. Las sesiones pegajosas (sticky sessions) atan a cada usuario a un servidor, lo que es sencillo pero frágil, porque si ese servidor cae se cierra la sesión de todos los que estaban en él, y complica el escalado. Un almacén de sesiones compartido, como Redis, Memcached o una base de datos, guarda las sesiones en un sitio central para que cualquier instancia pueda leerlas. Es el enfoque moderno estándar: mantiene la revocación inmediata, sobrevive a los reinicios y escala en horizontal, a cambio de una consulta de red rápida por petición y de un almacén que tienes que mantener disponible. Y el estado firmado en el cliente, el extremo JWT del espectro, elimina el almacén por completo a costa de la revocabilidad. “Las sesiones en el servidor no escalan” es folclore antiguo. Un almacén compartido aguanta volúmenes enormes sin perder el control que hace atractivas a las sesiones opacas, así que el compromiso real es operativo, mantener un almacén, frente a seguridad, conservar la revocabilidad, y en la mayoría de las apps de alto valor gana el almacén.


Qué debería hacer de verdad el cierre de sesión

El artículo de SSO mostró lo difícil que es cerrar sesión entre aplicaciones. Dentro de una sola app también hay que hacerlo bien, o es teatro, como demostró el informe de error de David. Un cierre correcto invalida la sesión en el servidor, borrando o marcando el registro, para que la credencial esté muerta aunque sobreviva una copia de la cookie. Borrar la cookie en el navegador no basta: si la sesión que hay detrás sigue siendo válida, una cookie capturada vuelve a entrar sin problema. Un buen cierre también borra la cookie en el cliente para que la siguiente visita empiece limpia, e idealmente ofrece “cerrar sesión en todas partes”, invalidando todas las sesiones del usuario en todos sus dispositivos, algo que la técnica de la versión de token hace barato. Y recuerda la frontera del SSO: cerrar sesión en la app no es lo mismo que cerrarla en el proveedor de identidad, y la interfaz debe decir con honestidad cuál de las dos acaba de ocurrir.


El arreglo de David fue pasar la app web a sesiones opacas en el servidor, guardadas en Redis, borrar el registro de la sesión al cerrar sesión y dar a los JWT que quedaban en la API una vida de diez minutos con refresh tokens revocables. El botón de atrás del ordenador de la biblioteca ahora muestra la pantalla de inicio de sesión.


La vida de una sesión, de principio a fin

Un flujo lo une todo. Sara inicia sesión en su banca online. Se autentica con una passkey y, si todo va bien, el servidor emite un ID de sesión nuevo, sustituyendo al anónimo, y lo fija como cookie HttpOnly; Secure; SameSite=Lax, mientras registra una sesión en el servidor con su identidad, sus roles y las marcas de tiempo. Mientras navega, cada petición trae de vuelta la cookie, el servidor la busca y su reloj de inactividad de 15 minutos se reinicia una y otra vez. Cuando empieza una transferencia, la app no se fía de la sesión que ya tiene; fuerza una autenticación escalonada, vuelve a comprobar su passkey en ese momento y rota otra vez el ID de la sesión ya elevada. Cuando se levanta y se va, el tiempo de inactividad termina la sesión en el servidor a los 15 minutos, y su siguiente clic la lleva a la pantalla de inicio de sesión; aunque hubiera seguido activa, un tiempo absoluto de ocho horas le habría pedido tarde o temprano que volviera a iniciar sesión. Y cuando esa noche avisa de que ha perdido el portátil, el servicio de atención revoca sus sesiones en el servidor, así que cualquier cookie que quede en el portátil falla en la siguiente petición. Como el banco eligió sesiones en el servidor, eso es inmediato. Un diseño basado en JWT sin estado tendría que esperar a que caducara el token o consultar una lista de denegación.


Lista de comprobación

Una lista corta para pasar a cualquier aplicación:


  • Cookies con HttpOnly, Secure, SameSite=Lax o Strict, un Domain y un Path estrechos y sin persistencia innecesaria.
  • ID de sesión de alta entropía y opacos, rotados al iniciar sesión y en cada cambio de privilegios, y nunca en una URL.
  • Un tiempo de inactividad corto y un límite absoluto más largo, los dos aplicados en el servidor, más autenticación escalonada para las acciones sensibles dentro de una sesión válida.
  • Una forma fiable de terminar una sesión ya desde el servidor; con JWT, vidas cortas y refresh tokens revocables.
  • Defensas contra el XSS (codificación de la salida, validación de la entrada, una CSP fuerte) y contra el CSRF (SameSite más tokens anti-CSRF en las peticiones que cambian estado).
  • TLS en todas partes, sin enviar ni registrar nunca ID de sesión en claro.
  • Tokens de “recordarme” separados, rotatorios, revocables y con menos privilegios, y en los clientes que no son navegadores, tokens en almacenamiento seguro, access tokens cortos con refresh tokens y DPoP donde encaje.

Resumen

La sesión es donde sigue viviendo la autenticación después del momento del inicio de sesión, y es un objetivo de primer orden, así que merece ingeniería deliberada. Viaja en cookies, y su seguridad está en los atributos: HttpOnly, Secure, SameSite y un Domain y un Path ajustados. Elegir entre tokens en el servidor y JWT es elegir entre revocabilidad y control o ausencia de estado y escala, y un JWT de larga duración como sesión entera es un antipatrón. El tiempo de inactividad y el absoluto trabajan juntos, uno cazando el abandono y el otro limitando la vida y forzando reautenticaciones periódicas. La revocación es inmediata con estado en el servidor y realmente difícil con JWT sin estado, así que “cerrar sesión ya” siempre necesita algo de estado en el servidor, más la rotación del ID para frenar la fijación. Y el secuestro, el XSS y el CSRF tienen cada uno controles específicos que encajan entre sí, y ninguno basta por sí solo.



Tres preguntas para ponerte a prueba

  1. Un compañero propone guardar el perfil completo del usuario y sus roles en un JWT válido durante 24 horas “para no tener una base de datos de sesiones”. Describe dos situaciones concretas en las que esto sale mal y cómo lo rediseñarías manteniendo casi toda la escalabilidad.
  2. Diseña la política de tiempos de una app de banca online: elige un tiempo de inactividad y uno absoluto, y nombra dos acciones que deberían forzar la reautenticación aunque la sesión esté a medias. Justifica cada número.
  3. Una app fija su cookie de sesión sin HttpOnly y sin SameSite. Nombra el ataque que expone cada atributo que falta y el control adicional que añadirías además de arreglar los atributos.

Ejercicios prácticos

  1. Mira una cookie de sesión real. En una web que uses, abre las herramientas de desarrollo y lee los atributos de la cookie de sesión. Anota cuáles de HttpOnly, Secure y SameSite están puestos, y escribe una frase sobre el riesgo de los que falten.
  2. Decodificar frente a consultar. Busca un JWT (muchas apps guardan uno en el almacenamiento local o lo envían en una cabecera Authorization) y decodifica su contenido con un depurador de JWT. Localiza su exp. Si te robaran este token ahora mismo, ¿cuánto tiempo seguiría siendo válido y podría el servidor cortarlo antes?
  3. Sigue un cierre de sesión. Cierra sesión en una app web con la pestaña de red abierta. Averigua si la sesión terminó en el servidor (la cookie antigua ya falla) o solo se borró en el navegador (el valor seguiría funcionando si se reutilizara). ¿Qué hizo la app?

Preguntas frecuentes

¿Qué hacen los atributos HttpOnly, Secure y SameSite de una cookie?

HttpOnly oculta la cookie a JavaScript, así que un ataque XSS no puede leer el ID de sesión. Secure envía la cookie solo por HTTPS e impide que se intercepte en claro. SameSite (Strict, Lax o None) decide si la cookie acompaña a las peticiones entre sitios, y es la principal defensa del navegador contra el CSRF. Una cookie de sesión debería ser HttpOnly, Secure y SameSite=Lax o Strict.

¿Qué diferencia hay entre un token de sesión en el servidor y un JWT para las sesiones?

Un token de sesión en el servidor es un ID aleatorio y opaco que apunta a un estado de sesión guardado en el servidor, así que el servidor es la fuente de verdad y puede revocarlo al instante borrando el registro. Un JWT es un token firmado y autocontenido que el servidor valida sin consultar nada, lo que escala bien pero es difícil de revocar antes de que caduque, porque no hay nada guardado que borrar. Se elige entre revocabilidad y control o ausencia de estado y escala.

¿Qué diferencia hay entre el tiempo de inactividad y el tiempo absoluto?

El tiempo de inactividad (deslizante) termina la sesión tras un periodo sin actividad y se reinicia con cada petición, así que quien está trabajando sigue dentro. El tiempo absoluto termina la sesión un tiempo fijo después del inicio de sesión, haya actividad o no, y pone un límite a su duración total. Los sistemas robustos usan los dos: uno de inactividad corto para cazar sesiones abandonadas y un límite absoluto más largo para forzar reautenticaciones periódicas.

¿Cómo se revoca una sesión antes de que caduque?

Con sesiones en el servidor, borras o marcas el registro de la sesión y la siguiente petición falla la validación al momento. Con JWT sin estado no hay nada que borrar, así que o mantienes vidas cortas con refresh tokens revocables, o mantienes una lista de denegación o una versión de token, lo que vuelve a introducir una consulta al servidor. La revocación inmediata siempre necesita algo de estado en el servidor.

¿Cuáles son los principales ataques contra las sesiones?

El secuestro de sesión (robar un ID de sesión válido por interceptación, XSS o una cookie insegura), el cross-site scripting (XSS, inyectar un script que lee o abusa de la sesión) y la falsificación de peticiones entre sitios (CSRF, engañar al navegador para que envíe una petición autenticada). Las defensas básicas son las cookies HttpOnly, Secure y SameSite, la codificación de la salida y la CSP contra el XSS, los tokens anti-CSRF y rotar el ID de sesión cada vez que cambian los privilegios.