¿Cuáles son los errores comunes con JWT — confusión de algoritmo (alg:none), falta de revocación, almacenamiento inseguro?
Un JWT es tan seguro como el código que lo verifica — y las tres formas más comunes de verificarlo mal terminan en un bypass total de autenticación.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cuáles son los errores comunes con JWT — confusión de algoritmo (alg:none), falta de revocación, almacenamiento inseguro?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Qué es realmente un JWT, y por qué esa forma es peligrosa
Un JSON Web Token son tres segmentos en base64url unidos por puntos: header.payload.signature. El header declara metadatos sobre el propio token — de forma crítica, qué algoritmo se usó para firmarlo (alg). El payload lleva los claims (sub, role, exp, lo que sea que el emisor haya puesto ahí). La firma se calcula sobre el header y el payload usando una clave que controla el servidor. La parte peligrosa es estructural: el header forma parte del token, lo que significa que es entrada controlada por el atacante, exactamente igual que el payload. Todos los errores de JWT que siguen vienen de código de verificación que confía en algo del header o del payload antes de que la firma se haya comprobado de verdad.
#Error 1: confusión de algoritmo y alg: none
La especificación de JWT define none como un valor de algoritmo legal, que significa "este token no está firmado en absoluto". Existe para casos de uso muy limitados y no productivos, pero si un verificador lee alg del token y despacha su lógica de verificación según ese valor, un atacante puede simplemente reescribir el header a {"alg":"none"}, eliminar la firma por completo y cambiar cualquier claim que le convenga — incluyendo role: "admin".
Una variante más sutil afecta a sistemas que emiten tokens asimétricos (RS256) firmados con una clave privada y verificados con una clave pública. Si el verificador acepta tanto HS256 como RS256 y usa el mismo material de clave para ambos, un atacante puede fabricar un token con alg: "HS256" y firmarlo usando la clave pública de RS256 como secreto HMAC. Las claves públicas, por definición, no son secretas — a menudo se publican en un endpoint /.well-known/jwks.json — así que el atacante termina con un token "firmado" válidamente que el servidor va a aceptar.
// VULNERABLE — confía en el header en vez de fijar el algoritmoconst jwt = require('jsonwebtoken');function verifyToken(token) {const decoded = jwt.decode(token, { complete: true });if (decoded.header.alg === 'none') {// chequeos "solo para desarrollo local" como este han llegado a producciónreturn decoded.payload;}// Sin lista de algoritmos permitidos: si este mismo servidor también// acepta tokens HS256 en otro lugar, un atacante puede firmar con la// clave pública de RS256 como secreto HMAC y pasar esta verificación.return jwt.verify(token, PUBLIC_KEY);}
// CORREGIDO — fija el algoritmo explícitamente; nunca confíes en el header,// nunca llames a decode() cuando el resultado se usará para una decisión de confianzaconst jwt = require('jsonwebtoken');function verifyToken(token) {return jwt.verify(token, PUBLIC_KEY, {algorithms: ['RS256'], // rechaza tanto "none" como HS256issuer: 'https://auth.example.com',audience: 'api.example.com',clockTolerance: 5,});// jwt.verify() lanza una excepción ante firma inválida, algoritmo// incorrecto o expiración — trata cualquier excepción como "no autenticado", punto.}
#Error 2: sin revocación integrada
La gran ventaja de un JWT — no necesitar consultar una base de datos para verificarlo — es también su mayor debilidad operativa. Una vez emitido, un JWT válido sigue siendo válido hasta exp, pase lo que pase después: un cambio de contraseña, una degradación de rol, un baneo de cuenta, un dispositivo robado que se reporta. No existe un DELETE para una firma.
Las mitigaciones prácticas reintroducen, a propósito, una pequeña cantidad de estado:
- Mantener los access tokens de vida corta (5-15 minutos) para que la ventana de exposición ante cualquier problema sea naturalmente pequeña.
- Llevar un
jti(JWT ID) o untokenVersionpor usuario en un almacén rápido; incrementarlo al cambiar la contraseña o al cerrar sesión explícitamente, y comprobarlo en cada verificación para las operaciones que de verdad necesitan revocación instantánea. - Combinar access tokens de vida corta con un refresh token de vida larga rastreado por el servidor — el refresh token es lo que realmente se revoca; al access token simplemente se le deja expirar.
#Error 3: guardar el token de forma insegura en el cliente
Dónde guarda el cliente un JWT determina qué clase de ataque puede robarlo:
localStorage/sessionStorage— legible por cualquier JavaScript que corra en la página. Un solo bug de XSS en cualquier parte de la app — un script de terceros, un comentario de usuario sin escapar, una dependencia vulnerable — permite a un atacante leer todos los tokens guardados y exfiltrarlos.- Una variable de JS guardada solo en memoria — se pierde al refrescar, pero es inmune a ser leída por un script ajeno después del hecho (aunque sigue siendo legible mientras un payload de XSS se está ejecutando activamente).
- Una cookie
HttpOnly— totalmente invisible para JavaScript, así que un payload de XSS estándar no puede leerla directamente, pero ahora viaja automáticamente en cada request, lo que reabre el CSRF como preocupación salvo que se combine conSameSitey/o un token CSRF.
| Error | Causa raíz | Mitigación |
|---|---|---|
Se acepta alg: none | El verificador confía en el header controlado por el atacante en vez de fijar el algoritmo | Pasar siempre una lista explícita algorithms: [...] a verify(); nunca llamar a decode() para una decisión de confianza |
| Confusión de algoritmo (RS256 → HS256) | El mismo material de clave se acepta tanto bajo un algoritmo asimétrico como simétrico | Fijar exactamente una familia de algoritmo por clave; nunca dejar que un mismo endpoint acepte HS* y RS*/ES* a la vez |
| Sin revocación | La verificación de firma nunca toca la base de datos | Access tokens de vida corta + comprobación de jti/versión de token donde la revocación instantánea realmente importa |
| Almacenamiento inseguro en el cliente | El token está en localStorage, legible por cualquier script inyectado | Guardar los access tokens en memoria; poner los refresh tokens en una cookie HttpOnly, Secure, SameSite |
| Datos sensibles en el payload | El payload del JWT está codificado en base64url, no encriptado | Nunca poner secretos/PII en los claims; usar JWE si el propio payload debe ser confidencial |
🧠 Check your understanding
0/3 · 0/3 answered1. Un verificador de JWT lee el algoritmo a usar desde el propio campo header del token antes de comprobar ninguna firma. ¿Qué ataque habilita esto directamente?
2. ¿Por qué no se puede revocar al instante un JWT comprometido en un diseño puramente sin estado?
3. ¿Cuál es el riesgo principal de guardar un access token JWT en localStorage?