Cómo decodificar y depurar un JWT
Un JSON Web Token son solo tres partes codificadas en Base64URL. Una vez que sabes leerlas, la mayoría de los errores de «token inválido» se explican solos en segundos. Esta guía muestra cómo decodificar un JWT, revisar los claims que suelen fallar y los errores que convierten los JWT en agujeros de seguridad.
De qué está formado un JWT
Un JWT tiene este aspecto: xxxxx.yyyyy.zzzzz. Las tres partes, separadas por puntos, son:
- Header — JSON con el algoritmo de firma (
alg) y el tipo de token, por ejemplo{"alg":"HS256","typ":"JWT"}. - Payload — JSON con los claims: a quién se refiere el token y cuándo es válido.
- Firma — un hash de las dos primeras partes, generado con un secreto (HMAC) o una clave privada (RSA, ECDSA).
Las dos primeras partes solo están codificadas, no cifradas. Cualquiera que tenga el token puede leerlas, por eso nunca debes poner contraseñas ni datos personales en el payload de un JWT.
Decodificar un token
Pega el token en el JWT Decoder y te mostrará el header, el payload y todas las marcas de tiempo convertidas en fechas legibles. Funciona en tu navegador, así que los tokens de producción no se envían a ningún sitio. Si prefieres hacerlo a mano, decodifica en Base64URL cada parte —reemplaza - por +, _ por / y añade el padding— o usa Node:
const [header, payload] = token.split('.').slice(0, 2)
.map(part => JSON.parse(Buffer.from(part, 'base64url').toString()));
console.log(header, payload);
Los claims que más fallan
| Claim | Significado | Error típico |
|---|---|---|
exp | Expiración, en segundos Unix | «jwt expired»: revisa también los relojes del servidor y del cliente |
nbf | No válido antes de | Un token usado unos segundos antes porque el reloj del emisor va adelantado |
iat | Emitido en | Rechazado como «emitido en el futuro» por desfase de reloj |
aud | Audiencia a la que va dirigido el token | Un token de una API enviado a otra: el verificador debe comprobarlo |
iss | Emisor | Emisores de desarrollo y producción mezclados |
Las marcas de tiempo están en segundos, no en milisegundos. Si una expiración parece del año 55 000, alguien escribió Date.now() en lugar de Math.floor(Date.now() / 1000). El Unix Timestamp Converter lo aclara. La mayoría de las librerías aceptan una pequeña tolerancia de reloj (30–60 segundos) para absorber el desfase entre servidores.
¿Por qué «invalid signature»?
- Secreto o clave incorrectos — la causa más habitual. Revisa la variable de entorno del servidor que verifica, no solo la del que firma.
- Algoritmo incorrecto — un token firmado con RS256 que se verifica como HS256, o al revés.
- El token fue editado — cambiar un solo carácter del payload rompe la firma, y eso es precisamente el objetivo.
- Rotación de claves — las claves del emisor cambiaron. Con JWKS, relaciona el
kiddel header con una clave publicada.
Para comprobar a mano un token HS256, calcula HMAC-SHA256 sobre header.payload con el secreto en el HMAC Generator y compáralo, codificado en Base64URL, con la tercera parte.
Errores de seguridad que debes evitar
- Decodificar no es verificar. Nunca confíes en los claims de
jwt.decode(); usa la función de verificación de tu librería con una lista explícita de algoritmos permitidos. - Rechaza
alg: noney fija el algoritmo: nunca dejes que el token decida cómo se comprueba. - Mantén los tokens de vida corta (minutos para los tokens de acceso) y usa refresh tokens para sesiones largas; un JWT no se puede revocar antes de caducar sin estado adicional en el servidor.
- No guardes tokens en localStorage si un fallo de XSS podría leerlos; para aplicaciones web, una cookie HttpOnly, Secure y SameSite es más segura.
¿Necesitas un token de prueba con claims concretos? Créalo con el JWT Encoder.