4 puntos por GN⁺ 18 시간 전 | 2 comentarios | Compartir por WhatsApp
  • JWT no es adecuado para mantener a los usuarios con la sesión iniciada, y para ese propósito una sesión con cookies común es más apropiada
  • La especificación de JWT asume tokens de vida corta, de alrededor de 5 minutos o menos, mientras que una sesión necesita una vida útil más larga
  • La autenticación sin estado segura es difícil de lograr, y para manejar tokens de forma segura al final se necesita algún tipo de almacenamiento de estado
  • Un JWT que solo contiene un token de sesión simple es menos eficiente y menos flexible que una cookie de sesión común, y las credenciales de autenticación no deben almacenarse en localStorage ni en sessionStorage
  • Si necesitas tokens firmados de vida corta, PASETO, diseñado pensando en la seguridad, es una mejor opción, pero no debe usarse para sesiones

Resumen clave

  • JWT no debe usarse para mantener a un usuario con la sesión iniciada, y para ese propósito una sesión con cookies común es una mejor herramienta
  • JWT no fue diseñado para ese objetivo y no es seguro, mientras que una sesión con cookies estándar es más adecuada para mantener sesiones de inicio de sesión
  • Como tema relacionado, las credenciales de autenticación, incluidos los tokens JWT, no deben almacenarse en localStorage ni en sessionStorage
  • Se pueden ver presentaciones relacionadas con JWT, pero otros temas como la protección CSRF suelen tratarse de forma breve, por lo que conviene aprenderlos por separado en otras fuentes
  • Incluso los casos de uso “válidos” de JWT al final del video pueden resolverse fácilmente con herramientas mejores y más seguras; en concreto, PASETO

Por qué hay que evitar JWT

  • La especificación de JWT fue diseñada solo para tokens de vida muy corta, de alrededor de 5 minutos o menos, y una sesión necesita una vida útil mayor
  • La autenticación sin estado de forma segura no es posible, y para procesar tokens de manera segura necesariamente hace falta cierto estado
    • Si se necesita un almacén de datos, es mejor guardar todos los datos que manejar solo parte del estado de los tokens
    • Este problema se trata con más detalle en http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
    • En la práctica hay aplicaciones que usan JWT de esta forma, pero esas aplicaciones son defectuosas, así que no hay que repetir el mismo error
  • Un JWT que solo guarda un token de sesión simple es menos eficiente y menos flexible que una cookie de sesión común, y no aporta beneficios adicionales
  • La propia especificación de JWT no goza de la confianza de los expertos en seguridad, por lo que debe excluirse de cualquier uso relacionado con seguridad y autenticación

Objeciones

  • La objeción de “Google también usa JWT” no se aplica a las sesiones de usuario en el navegador
    • Google no usa JWT para las sesiones de usuario en el navegador, sino sesiones normales con cookies
    • JWT solo se usa como medio de transporte de Single Sign On para transferir una sesión de inicio de sesión de un servidor o host a la sesión de otro servidor o host
    • Ese modo de uso sí entra dentro de los casos de uso razonables de JWT
    • Google cuenta con los recursos y los expertos en seguridad para crear y mantener una implementación de JWT más segura
    • En la práctica, el JWT de Google no es lo mismo que el JWT que se usa en otros lugares
  • La objeción de que “sin estado es mejor” no encaja con los requisitos de una autenticación segura
    • Sin enormes recursos, no es posible operar de forma segura una autenticación realmente sin estado
    • Para una discusión relacionada, puedes consultar Stateless is a lie
  • El problema de “no sé cómo configurar sesiones” puede resolverse en la mayoría de los casos con la documentación y las implementaciones de los frameworks de servidor web
    • La tecnología de sesiones no es especialmente nueva, por lo que no es común ver artículos que la expliquen con frecuencia
    • La documentación de la implementación de sesiones debería bastar para seguir el proceso de configuración
    • Casi todos los frameworks de servidor web incluyen una implementación de sesiones y, aunque no venga activada por defecto, normalmente puede habilitarse con facilidad
    • Express y otros frameworks de Node.js son una especie de excepción por su alta modularidad y su enfoque de propósito único
    • En Express basta con usar el middleware express-session y un store connector adecuado para el almacenamiento
    • Se recomienda usar connect-session-knex junto con Postgres, MySQL o, si es posible, SQLite

Tokens de corta duración

  • Si necesitas tokens firmados de vida corta para algún caso de uso, existe una mejor especificación diseñada pensando en la seguridad: PASETO
  • Incluso usando PASETO, no debe emplearse para sesiones

Cómo funcionan las sesiones

  • Si quieres aprender más sobre cómo funcionan las sesiones, conviene revisar el gist de joepie91

2 comentarios

 
shj5508 9 시간 전
  1. El JWT es una forma de cifrar tokens y reducir consultas a la BD; no es un concepto opuesto a la autenticación con cookies. Si guardas el JWT en una cookie secure, el riesgo de robo es el mismo que en el método legado de autenticación por cookies.

  2. Gestionar una lista de expiración para hacer que el JWT expire sí tiene ventajas de rendimiento. Hay una diferencia de costo entre consultar solo la información de expiración en Redis y consultar en la BD a todos los usuarios.

Decenas de miles de consultas basadas en índice sobre 100 mil filas de miembros (método legado con cookies)
vs
Decenas de miles de consultas a 50 elementos de la lista de expiración en Redis (método de expiración inmediata con JWT)

Sí hay ventajas en JWT. Solo que en entornos pequeños la diferencia se nota menos.

 
Opiniones en Hacker News
  • Falta una pista importante: se está hablando de sesiones de usuario basadas en navegador
    En la comunicación entre servicios, muchas veces sí se puede usar JWT bien
    Además, leí parte del artículo enlazado y hay textos como, por ejemplo, https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba.... Si JWT fuera un estándar tan horriblemente inseguro, bastaría con publicar cómo hackear AssumeRoleWithWebIdentity de AWS STS o, mejor, no publicarlo y poner mineros de criptomonedas en las cuentas AWS de producción de todas las Fortune 500. Si JWT es tan inseguro, avisen cuando lo logren /sarcasmo

    • Esta sí me parece exactamente la conclusión razonable. Estoy de acuerdo en que JWT es una herramienta equivocada para sesiones de usuario en el navegador
      La parte de firma/cifrado de JWT es compleja, y aunque las bibliotecas comunes de JWT por fin ya medio entraron en razón, antes no era así. Muchas aceptaban el algoritmo "none" [1], y en otros casos usaban una clave pública como si fuera un secreto compartido, permitiendo que un atacante falsificara tokens [2]. Es una consecuencia directa de la complejidad que critica el artículo enlazado
      Además, JWT a veces ni siquiera ofrece lo que se quiere para sesiones de usuario. No se puede invalidar sin tener una lista de revocación en algún lado. Pero si en cada request tienes que comparar un identificador contra una lista de revocación, entonces mejor usa un ID de sesión opaco y consúltalo en cada request. Claro, se pueden usar tokens de vida corta y renovarlos continuamente, pero en aplicaciones normales que de todos modos necesitan mantener estado, no hay mucha razón para hacerlo
      Dicho eso, estoy totalmente de acuerdo en que los tokens firmados pueden ser útiles en sistemas distribuidos o en comunicación entre máquinas. No hay que confundir ambos casos
      [1] https://nvd.nist.gov/vuln/detail/cve-2022-23540
      [2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150
    • Antes, JWT tenía muchos problemas por culpa de bibliotecas con malos valores predeterminados. Hace unos años, los ataques de downgrade también eran bastante comunes
      Ahora las principales bibliotecas en varios lenguajes ya traen defaults más sensatos, así que hoy en día diría que en la práctica se ha vuelto bastante seguro
    • JOSE puede seguir causando problemas aunque esté implementado correctamente. Muchas veces la superficie de API relacionada no es muy buena
      Si “es seguro si lo agarras y lo usas correctamente” significara automáticamente que es un buen diseño, entonces lo mismo aplicaría a algo como X.509
      En muchos casos hay alternativas mejores. Los tokens de sesión estándar o las API keys se usan ampliamente en la mayoría de los sitios web grandes, y encajan casi perfectamente para la mayoría de los usos
      No quiero decir que este estándar no tenga ningún valor. Lo mejor que tiene es ser un estándar básico para intercambiar algo sin recurrir a codificación ASN.1, y las herramientas del lado de ASN.1 parecen extremadamente frágiles y llenas de bugs
    • Estoy de acuerdo con la primera parte, pero lo añadido es una falacia lógica. No hace falta poder hackear algo para decir que no es seguro
      Por ejemplo, no sé cómo abusar de SAML, pero sí sé que es un estándar horrible porque convierte todo el parser XML en superficie de ataque. No soy investigador de seguridad ni sé encontrar vulnerabilidades en parsers XML, pero sí puedo entender que una superficie de ataque grande es mala
    • La línea de vulnerabilidades causadas por JWT en aplicaciones reales viene desde hace mucho tiempo
  • ¿Qué quieres decir con que JWT no es seguro? ¿Incluso usando un esquema de firma RSA/clave pública confiable? ¿Aunque no sea un secreto compartido?
    También se me hace rara la afirmación de que JWT vive demasiado tiempo. Puedes limitar la vida útil del JWT y tener un modelo de renovación con una autoridad de autenticación. Incluso si usas sesiones basadas en cookies, al final igual estás guardando algo en alguna parte. Puedes hacer que un JWT sea válido solo por 5 a 15 minutos, y 15 minutos es parecido al tiempo de caché de varios sistemas de autorización, incluido Entra. Incluso un token de 5 minutos puede funcionar bien en el navegador si tienes un sistema de renovación
    Por último, prefiero separar identidad/autenticación de la aplicación o servicio API. Te permite externalizar el contexto, y procesar JWT en cada request es más fácil de manejar que un sistema compartido de caché/estado que puede fallar de forma intermitente. Los tokens firmados permiten verificar la firma contra una autoridad conocida

    • Un JWT puede hacerse inválido en 30 segundos, o incluso en 1 segundo. Al crear un JWT, debes establecer la audience
      Fuera de eso, la firma es criptográficamente válida. Basta con validar todos los JWT siempre, usando vidas útiles cortas
      Como referencia, todos los tokens OIDC son JWT
  • Si comparas sesiones con una lista de revocación de JWT, también hay una lógica a favor de la lista de revocación de JWT. Como los JWT tienen una expiración limitada, solo necesitas mantener en la lista los tokens que todavía no han expirado
    Es probable que los JWT revocados sean solo una fracción de los JWT válidos que están circulando, así que en cada request solo consultas un dataset muy pequeño
    Si usas sesiones, la lista de sesiones válidas probablemente será varios órdenes de magnitud mayor que la lista de revocación, así que el costo de almacenamiento y de consulta asociado al estado será más alto
    Además, el artículo dice que JWT es stateless, pero normalmente no lo es. Por lo general no solo validas el JWT, sino que en cada request obtienes el objeto de identidad correspondiente, o sea los detalles del usuario, para verificar que siga activo y que tenga permiso para realizar esa acción. Se puede usar una lista de revocación por usuario o un valor como minimum_issued_at para validar el campo iat del JWT. Esto también permite el patrón de “cerrar sesión en todos los dispositivos”: basta con poner el minimum_issued_at del usuario en $NOW y así se revocan todos los tokens anteriores. No hace falta consultar una lista de revocación individual

    • En el momento en que tienes que consultar el objeto del usuario, la ventaja principal de JWT ya desapareció y simplemente se puede descartar
    • Consultar datos de sesión es un select con índice en la base de datos, que devuelve 0 o 1 filas. En la mayoría de los casos no es algo de qué preocuparse
    • Las sesiones también tienen tiempo de expiración, y puedes configurarlo como quieras
  • Este texto enlaza la mayor parte del “por qué” a otras entradas de blog, pero esas entradas en general parecen estar molestas por el hecho de que “no se pueden invalidar tokens JWT individuales”.
    Cada vez que lo implementé, la guía habitual era verificar en algún lugar los nonce invalidados, y con eso también se resuelve la segunda afirmación de ese texto.
    La frase “los expertos en seguridad no confían en la especificación JWT en sí” parece necesitar más sustento que una sola entrada de blog. Y ese texto en general parece culpar a implementaciones malas, pero los problemas de mala implementación acompañan a cualquier estándar.
    En general, no sé qué esperaba al hacer clic en un enlace aleatorio a un gist.

    • Algunas implementaciones iniciales permitían configurar cualquier emisor en el encabezado y confiar en él tal cual, pero obviamente eso estaba mal desde el principio. Si solo permites emisores confiables o “conocidos”, desaparecen muchas preocupaciones contextuales.
      Además, en el navegador se pueden usar JWT de vida más corta sin problema y hacer que el agente los renueve por sí solo. Si usas Azure Entra u otros proveedores, de hecho así funciona. Puedes mantener el JWT relativamente corto, unos 5 a 15 minutos, y además verificar si el jti fue revocado.
      Los JWT son muy útiles para separar y reutilizar la autoridad de acceso del sistema de aplicación/API. Mueves la superficie de ataque, pero la mueves de una manera confiable. En todo el mundo se usa criptografía de clave pública en muchos lugares, incluido SSH. No usaría secretos compartidos ni tokens de larga duración, pero los tokens firmados con clave pública, de vida corta y provenientes de una fuente verificada y conocida, en general están bien.
      De hecho, muchas veces lo que sí da problemas reales son las API keys. Justo tuve que implementar eso, y en mi caso hice que la API key se viera como un Bearer token, con un prefijo corto sak., seguido de una parte de identidad (bytes UUID en base64url) y luego un valor secreto (bytes en base64url). En la base de datos guardo el UUID y un salt+hash de nivel passphrase generado a partir del valor secreto. Así que la API key generada debe tratarse como un secreto, y en la base de datos solo queda almacenada de forma unidireccional, de modo que una filtración de la base de datos no se convierte automáticamente en una filtración de autenticación.
      Aun así, es mucho más probable una filtración de API keys que un problema en una solución JWT bien implementada.
    • No estoy 100% de acuerdo con la afirmación de que “no se pueden invalidar tokens JWT individuales”. Para mí, al implementarlo, verificar en algún lugar los nonce invalidados es sentido común, y me sorprende cada vez que vuelvo a descubrir que la gente no lo hace.
  • Me topé con este texto por casualidad, y como trabajé mucho en este tema hace tiempo, me pareció interesante que vuelva a ser tema ahora. Pero cuando hice clic vi que el autor enlazaba parte de mi material. Qué recuerdos de hace muchísimo tiempo.
    En fin, personas mucho más inteligentes que yo han tratado este tema ampliamente durante años, pero incluso en 2026 sigo pensando que JWT es una herramienta incorrecta para autenticación web. Está bien para uso entre servicios, pero si tienes elección, mejor usa PASETO. Resuelve muchos problemas.

  • Ahora mismo le estoy agregando RabbitMQ al sitio para push notifications. Estoy usando autenticación JWT para controlar qué puede leer el cliente y desde dónde, con vida corta y renovación periódica del token.
    No conozco otra configuración que se acerque a la facilidad de este esquema. Basta con agregar un endpoint que entregue un token JWT a las sesiones válidas, y también puedes tener permisos por usuario.

  • Uno de los textos enlazados que supuestamente explican por qué no deberías usar JWT es, siendo generosos, raro.
    https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
    En resumen, dice “algunas librerías tenían bugs”, y luego recomienda traer libsodium y hacerlo tú mismo. Es un consejo tan absurdo que cuesta tomarlo en serio. Todo software tiene bugs. Con Heartbleed hubo caos en todo internet, y aun así seguimos usando TLS y OpenSSL.
    Nunca había oído eso de que “la especificación JWT fue diseñada especialmente solo para tokens de vida muy corta, de alrededor de 5 minutos o menos”, y tampoco encuentro evidencia que lo respalde. RFC 7519 no dice eso.

  • Normalmente uso JWT como una caché de autenticación. Obtienes un token de autenticación desde el servicio de autenticación, y ese token otorga permisos sobre otros servicios.
    Tiene varias ventajas, pero la principal es que los servicios secundarios no necesitan interactuar con la base de datos de autenticación ni tener autoridad para emitir tokens. Asumiendo que usas RS256 y no HMAC. Entonces, aunque un servicio secundario sea comprometido, no es tan grave como si se comprometiera un servicio con acceso a la base de datos de autenticación.
    Si hay datos sensibles dentro del token, entonces hay que usar JWE, pero eso no es tan bueno porque cada vez que lo usas tienes que pedirle a un servicio interno con la clave privada que descifre el token.
    Una estructura que uso con frecuencia es {"id": (uuid), "scopes": ["scope:read/write"]}.
    También va bastante bien para SPA. Porque el servidor de sitio estático puede verificar el JWE con una clave pública antes de entregar recursos. Mi enfoque es compilar el sitio estático en forma de /(scope)/path, para que el servicio estático ni siquiera entregue páginas a las que no se puede acceder. Es muy útil cuando no quieres exponer al usuario funciones que tiene el backend, como un panel de administración, o rutas de servicios internos que podrían ser atacadas.
    La vida del JWT para “acceso al backend” es de unos 5 minutos, y cosas como /me se almacenan en caché en localStorage salvo que /refresh indique explícitamente que se descarte esa caché. El request handler de la aplicación SPA detecta que “hace falta renovar” y renueva el token.
    Creo que gran parte de esta responsabilidad recae en las librerías de node/next y Python. El backend lo escribo en lenguajes fuertemente tipados, y el frontend siempre lo hago con páginas estáticas precompiladas. La configuración actual del frontend usa VITE, con landing en páginas prerenderizadas y la aplicación como una SPA normal.
    Incluso teniendo todo eso en cuenta, no estoy para nada de acuerdo con todo este gist. JWT puede hacerse tan seguro como quieras.

  • JWT está bien, y el título se ve un poco sensacionalista.
    En cambio, hay temas más interesantes para discutir: cuándo conviene usar valores cifrados (simétricos o asimétricos), valores aleatorios pero secretos, valores firmados (que pueden leerse pero no alterarse); dónde conviene guardar esos valores (memoria, localStorage, cookies); y cómo hacer para que esos valores no duren para siempre, además de si necesitas revocarlos antes de su expiración natural.