Ciberseguridad
Redefiniendo la Seguridad: Buenas Prácticas de JWT para Arquitecturas Distribuidas Modernas
Los JSON Web Tokens se han convertido en la columna vertebral de la autenticación sin estado en sistemas distribuidos, microservicios y aplicaciones de página única. Su portabilidad es genuinamente convincente: un token firmado carga todas las afirmaciones necesarias para autorizar una petición sin ningún viaje de ida y vuelta a un almacén de sesiones. Pero esta simplicidad esconde un conjunto de vulnerabilidades sutiles y de alto impacto que han comprometido sistemas reales a escala. Este artículo profundiza en las prácticas que realmente importan en entornos de producción con millones de peticiones diarias.
Qué Garantizan los JWT (y Qué No)
Un JWT es un token compacto, seguro para URL, compuesto por tres partes codificadas en Base64URL: una cabecera, un payload y una firma. La cabecera declara el algoritmo utilizado; el payload contiene claims (pares clave-valor arbitrarios); la firma los une usando un secreto o una clave privada. Lo que un JWT firmado garantiza es la integridad: si el token no ha sido manipulado, la firma se verificará. Lo que no garantiza es la confidencialidad. El payload es legible por cualquiera que intercepte el token.
Insight Técnico: Dado que Base64URL es codificación, no cifrado, cualquier claim en el payload de tu JWT es visible en texto plano para el navegador, para proxies intermedios y para cualquiera que realice un ataque MITM sobre HTTP. Si necesitas confidencialidad, requieres JSON Web Encryption (JWE), no JWS. La mayoría de los equipos despliegan JWS asumiendo confidencialidad — ese es un error crítico de diseño.
Escenario Práctico: Una plataforma SaaS empresarial embebió el nivel de suscripción del usuario en el payload para acelerar las verificaciones de autorización. Tras una prueba de penetración, descubrieron que cualquier usuario autenticado podía inspeccionar su propio JWT y ver los umbrales de precios exactos utilizados para las limitaciones de funcionalidades. La corrección fue directa (mover los metadatos sensibles a una consulta del lado del servidor), pero el daño a la confianza en el informe del incidente no lo fue.
El Ataque de Confusión de Algoritmos: Por Qué 'none' Nunca Es Aceptable
La especificación JWT permite que las librerías acepten el algoritmo none, pensado para casos donde la integridad del token está garantizada por un canal externo. En la práctica, esto ha dado lugar a una de las vulnerabilidades JWT más ampliamente explotadas jamás documentadas. El ataque es directo: un atacante toma un JWT legítimo, cambia la cabecera del algoritmo a none, elimina la firma y lo presenta. Si la librería confía en la cabecera y omite la verificación de firma cuando alg es none, el atacante acaba de escalar sus privilegios sin romper ninguna primitiva criptográfica.
El ataque relacionado es la confusión de algoritmos entre claves simétricas y asimétricas. Una librería configurada para RS256 (asimétrico) puede aceptar un token firmado con HS256 (simétrico) si el desarrollador pasa la clave pública como secreto HMAC. El atacante obtiene la clave pública (que está diseñada para distribuirse), la usa como secreto HS256 para firmar un payload falsificado y lo presenta al servidor, que lo verifica con éxito.
Insight Técnico: La mitigación requiere un allowlisting estricto de algoritmos en el lado de la verificación. No pases el algoritmo de la cabecera del token a la función de verificación. En su lugar, codifica el algoritmo esperado en la configuración del servidor. Esto ahora es un requisito en la mayoría de las librerías conformes tras el RFC 8725, pero las dependencias antiguas pueden seguir exponiéndolo.
Error Común: Usar los valores predeterminados de la librería sin auditar la ruta de cumplimiento del algoritmo. Antes de fijar versiones de la librería, verifica que la ruta del código de verificación rechaza explícitamente los tokens cuya cabecera alg no coincida con tu expectativa configurada.
Gestión de Claves: RS256 vs ES256 y Políticas de Rotación
La firma simétrica (HS256) requiere el mismo secreto tanto para la firma como para la verificación. Esto es aceptable para aplicaciones monolíticas donde ambas operaciones ocurren en límites de confianza que controlas completamente. En el momento en que un segundo servicio necesita verificar tokens —sin la capacidad de emitirlos— tienes un problema de distribución de claves. Ambas partes deben compartir el secreto, y cualquier filtración compromete todo el sistema.
Los algoritmos asimétricos resuelven esto separando la clave de firma (privada, guardada solo en el Auth Server) de la clave de verificación (pública, distribuida a cualquier parte que confíe). RS256 usa RSA de 2048 bits y es la línea base actual. ES256 usa criptografía de curva elíptica (P-256) y produce firmas más pequeñas con seguridad equivalente o mejor, una optimización relevante cuando los tokens se incluyen en cada cabecera de petición HTTP.
| Propiedad | HS256 (HMAC) | RS256 (RSA) | ES256 (ECDSA) |
|---|---|---|---|
| Tipo de clave | Simétrica | Asimétrica | Asimétrica |
| Tamaño de firma | 32 bytes | 256 bytes | 64 bytes |
| Verificación distribuida | No — secreto compartido | Sí — clave pública | Sí — clave pública |
| Rendimiento (verificación) | Muy alto | Medio | Alto |
| Recomendado para microservicios | No | Sí | Sí (preferido) |
La rotación de claves es innegociable en un sistema maduro. Rotar claves asimétricas requiere publicar la nueva clave pública a través de un endpoint JWKS antes de retirar la clave privada antigua. Los clientes que cachean la clave pública antigua por rendimiento fallarán en la validación si la rotación no es gradual. Un patrón común es publicar la nueva clave junto a la antigua con un claim kid (key ID), permitiendo a los clientes seleccionar la clave de verificación correcta por token hasta que la clave antigua sea completamente retirada.
Almacenamiento de Tokens en SPAs: El Patrón de Cookie httpOnly
La mala práctica de facto en las SPAs es almacenar el access token en localStorage. Cualquier script de terceros incluido en tu página —una librería de análisis, un widget alojado en CDN, una dependencia npm comprometida— puede ejecutar localStorage.getItem('access_token') y exfiltrarlo silenciosamente. Las vulnerabilidades XSS, que siguen siendo extraordinariamente comunes en aplicaciones front-end complejas, se traducen directamente en robo de tokens cuando se usa localStorage.
El patrón recomendado almacena el access token en memoria (una variable JavaScript en el estado de la aplicación) y almacena el refresh token en una cookie httpOnly, Secure, SameSite=Strict. Esta configuración significa que el refresh token nunca es accesible para JavaScript —ni siquiera el tuyo propio— y el navegador lo envía automáticamente solo sobre HTTPS al dominio exacto que lo emitió.
Consideración Arquitectónica: Este patrón requiere un endpoint ligero de refresco de token en el lado del servidor. Cuando el access token expira (típicamente después de 15 minutos), la SPA llama a este endpoint con la cookie httpOnly adjunta automáticamente por el navegador, y recibe un nuevo access token en el cuerpo de la respuesta. El propio refresh token se rota en cada uso, haciendo que los ataques de replay de tokens sean significativamente más difíciles de ejecutar sin ser detectados.
- Vida útil del access token: 5–15 minutos. Suficientemente corta para limitar el radio de daño en caso de filtración.
- Vida útil del refresh token: 7–30 días con rotación en cada uso.
- Almacena los access tokens solo en memoria, nunca en localStorage o sessionStorage.
- Almacena los refresh tokens en cookies httpOnly Secure SameSite=Strict.
- Implementa una lista de revocación de tokens (respaldada por Redis) para los casos que requieren invalidación inmediata.
Validación de Claims: Más Allá de la Verificación de Firma
Una firma válida significa únicamente que el token fue emitido por una entidad que posee la clave privada. No dice nada sobre si ese token está destinado a tu servicio, si ha expirado o si ha sido revocado. La validación de claims es un segundo paso obligatorio que muchos equipos omiten o implementan de forma incompleta.
Las validaciones mínimas requeridas son: exp (expiración — rechaza tokens cuyo exp esté en el pasado), iss (emisor — rechaza tokens no emitidos por tu Auth Server esperado), y aud (audiencia — rechaza tokens no destinados a tu servicio). El claim aud está consistentemente infraimplementado, lo que lleva a la reutilización de tokens entre servicios dentro de la misma organización. Un token emitido para el servicio de facturación debería ser rechazado por el servicio de inventario aunque ambos confíen en el mismo Auth Server.
Escenario de Arquitectura Real: Una plataforma fintech con 12 microservicios compartía un único Auth Server. Todos los servicios validaban correctamente iss y exp, pero ninguno validaba aud. Un atacante obtuvo un token válido para la API pública de bajos permisos y lo reprodujo contra el servicio de administración interno. Dado que ambos servicios confiaban en la misma autoridad de emisión y no se aplicaba ninguna restricción de audiencia, el ataque tuvo éxito. Implementar validación estricta de aud en todos los servicios resolvió el incidente.
Preguntas Frecuentes
- ¿Puedo revocar un JWT antes de que expire? No de forma nativa. El enfoque estándar es una expiración corta combinada con una lista de revocación respaldada por Redis, consultada en cada petición para operaciones sensibles.
- ¿Debo almacenar los roles de usuario en el payload del JWT? Solo datos de autorización estables y de baja sensibilidad (ID de usuario, rol). Evita incluir permisos granulares que cambien con frecuencia — los claims obsoletos crean deriva en la autorización.
- ¿Es JWT mejor que las sesiones para microservicios? Para la comunicación entre servicios donde un almacén de sesiones requeriría infraestructura compartida entre equipos, sí. Para monolitos tradicionales, los IDs de sesión opacos con búsqueda en el servidor son a menudo más simples y seguros.
- ¿Qué es la introspección de tokens y cuándo debo usarla? La introspección de tokens (RFC 7662) permite a un servidor de recursos validar un token consultando el Auth Server en tiempo de petición. Elimina problemas de claims obsoletos pero reintroduce acoplamiento de red. Úsala para endpoints de alta sensibilidad donde las garantías de frescura justifican la latencia.
- ¿Cómo debo manejar un compromiso de clave? Rota inmediatamente la clave de firma, invalida todos los tokens pendientes y fuerza la re-autenticación en todas las sesiones.
Conclusión
La autenticación basada en JWT no es inherentemente insegura, pero requiere decisiones de ingeniería deliberadas en cada capa: cumplimiento de algoritmos, gestión de claves, validación de claims y almacenamiento en el lado del cliente. La gran mayoría de las brechas reales de JWT se originan por ignorar una de estas capas, no por debilidades en el propio estándar. A medida que las arquitecturas distribuidas se convierten en la norma incluso para productos de tamaño mediano, consolidar estos fundamentos correctamente ya no es opcional. Los equipos que invierten en una infraestructura de tokens correctamente reforzada desde el principio se ahorran respuestas a incidentes que son órdenes de magnitud más costosas que el esfuerzo inicial.
Compartir este artículo
Suscríbete
Recibe los últimos artículos directamente en tu bandeja de entrada.
Deja un comentario