Seguridad de JSON Web Tokens: Vulnerabilidades Comunes y Soluciones
Los JWT son el estándar de facto para la autenticación de APIs, pero su aparente simplicidad oculta riesgos de seguridad reales. Los JWT mal configurados han provocado elusiones de autenticación, escalación de privilegios y filtraciones de datos. Esta guía cubre las vulnerabilidades más críticas y cómo prevenirlas.
Resumen de la Estructura JWT
Un JWT consta de tres partes codificadas en Base64URL separadas por puntos:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
- Header: Algoritmo y tipo de token
- Payload: Claims (datos del usuario, expiración, etc.)
- Signature: Verificación criptográfica
Inspecciona la estructura JWT con nuestro Codificador/Decodificador JWT.
Para una comprensión fundamental de la estructura JWT, consulta nuestra guía JWT Tokens Explicados.
Vulnerabilidades Críticas
1. Ataque de Confusión de Algoritmo
La vulnerabilidad JWT más peligrosa. Si un servidor acepta el header alg del token sin validación, un atacante puede:
Ataque: Cambiar el algoritmo de RS256 (asimétrico) a HS256 (simétrico) y firmar el token falsificado con la clave pública del servidor:
// Attacker's forged token
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// Signed with the server's PUBLIC key as the HMAC secret
Si el servidor verifica tokens HS256 usando la clave pública como secreto, el token falsificado pasa la verificación.
Solución: Siempre especifica el algoritmo esperado explícitamente:
// WRONG - accepts whatever algorithm the token specifies
jwt.verify(token, key);
// CORRECT - enforce specific algorithm
jwt.verify(token, key, { algorithms: ['RS256'] });
2. Ataque de Algoritmo None
Algunas bibliotecas aceptan "alg": "none" — un token sin firma:
// Forged token with no signature
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: "" // empty
Solución: Nunca permitas el algoritmo none en producción. Lista explícitamente los algoritmos permitidos.
3. Secretos de Firma Débiles
Los JWT basados en HMAC (HS256/HS384/HS512) son tan fuertes como el secreto:
// TERRIBLE - can be brute-forced in seconds
secret = "password123"
// WEAK - dictionary attack vulnerable
secret = "my-jwt-secret"
// STRONG - 256+ bits of randomness
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"
Solución: Usa al menos 256 bits de aleatoriedad criptográfica para secretos HMAC. Mejor aún, usa claves asimétricas (RS256, ES256) donde la clave de firma nunca necesita ser compartida.
4. Expiración Faltante
Los tokens sin expiración nunca caducan — un token robado otorga acceso permanente:
// WRONG - no expiration
{ "sub": "user123", "role": "admin" }
// CORRECT - short expiration
{
"sub": "user123",
"role": "admin",
"exp": 1705312800,
"iat": 1705309200,
"nbf": 1705309200
}
Mejor práctica: Los tokens de acceso expiran en 15-60 minutos. Usa refresh tokens (almacenados de forma segura) para sesiones largas.
5. Datos Sensibles en el Payload
Los payloads JWT están codificados en Base64URL, no cifrados. Cualquiera puede decodificarlos:
echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}
Nunca incluyas: Contraseñas, claves API, números de tarjetas de crédito, datos personales (número de seguro social, registros médicos) ni ningún secreto en los payloads JWT.
6. Almacenamiento de Tokens (XSS vs CSRF)
| Almacenamiento | Vulnerable a XSS | Vulnerable a CSRF |
|---|---|---|
| localStorage | Sí | No |
| Cookie (sin flags) | Sí | Sí |
| Cookie HttpOnly | No | Sí |
| Cookie HttpOnly + SameSite | No | No |
Recomendado: Almacena los JWT en cookies HttpOnly, Secure, SameSite=Strict. Esto previene el acceso desde JavaScript (defensa contra XSS) y las solicitudes entre sitios (defensa contra CSRF).
Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
Lista de Verificación de Seguridad
- Especifica algoritmos explícitamente — nunca aceptes del header del token
- Rechaza el algoritmo
none— siempre requiere una firma - Usa secretos fuertes — 256+ bits para HMAC, 2048+ bits para RSA
- Establece expiración corta — 15-60 minutos para tokens de acceso
- Valida todos los claims —
exp,iss,aud,nbf - Almacena en cookies HttpOnly — no en localStorage
- Rota los secretos periódicamente — planifica la rotación de claves
- Nunca almacenes datos sensibles en el payload
- Usa claves asimétricas para sistemas distribuidos (RS256 o ES256)
- Implementa revocación de tokens — lista negra o expiración corta + refresh tokens
Firma Asimétrica vs Simétrica
| Aspecto | HS256 (Simétrico) | RS256 (Asimétrico) |
|---|---|---|
| Clave | Secreto compartido | Par de clave privada/pública |
| Quién puede firmar | Cualquiera con el secreto | Solo el poseedor de la clave privada |
| Quién puede verificar | Cualquiera con el secreto | Cualquiera con la clave pública |
| Distribución de clave | El secreto debe compartirse de forma segura | Solo se comparte la clave pública |
| Rendimiento | Más rápido | Más lento |
| Ideal para | Servicio único | Microservicios, sistemas distribuidos |
Recomendación: Usa ES256 (ECDSA) para nuevas aplicaciones — proporciona seguridad asimétrica con rendimiento cercano a HMAC.
Estrategias de Revocación de Tokens
Los JWT son stateless por diseño — no hay forma integrada de revocarlos. Estrategias:
- Expiración corta: Si los tokens expiran en 15 minutos, la ventana de un token robado es limitada
- Rotación de refresh tokens: Emite un nuevo refresh token con cada uso; si un refresh token se usa dos veces, revoca todos los tokens
- Lista negra: Almacena los IDs de tokens revocados (claims jti) y verifica en cada solicitud
- Versionado de tokens: Incluye un número de versión en los claims; incrementa la versión del usuario al cerrar sesión
Preguntas Frecuentes
¿Debería usar JWT o cookies de sesión para autenticación?
Las cookies de sesión son más simples y más seguras para aplicaciones web tradicionales — el servidor controla el ciclo de vida de la sesión y la revocación es instantánea. Los JWT son mejores para APIs stateless, microservicios y aplicaciones móviles donde el almacenamiento de sesiones en el servidor es impráctico. Si eliges JWT, implementa las medidas de seguridad de esta guía.
¿Cuál es el tiempo de expiración ideal para JWT?
Para tokens de acceso: 15-60 minutos. Más corto es más seguro pero requiere renovación más frecuente. Para refresh tokens: 1-30 días, almacenados en cookies HttpOnly. Para tokens de un solo uso (verificación de correo, restablecimiento de contraseña): 1-24 horas.
Recursos Relacionados
- Codificador/Decodificador JWT — Inspecciona y decodifica JWTs de forma segura
- JWT Tokens Explicados — Comprende la estructura y el flujo de JWT
- Entropía de Contraseñas Explicada — Fortaleza de los secretos de firma JWT