Decodificador de Tokens JWT
Decodifica JSON Web Tokens y visualiza su contenido.
Decoding only — the signature is not verified. Nothing is sent anywhere.
Descripción general
Un JWT son tres partes codificadas en base64url y separadas por puntos: una cabecera que dice cómo se firmó, un payload de claims y una firma sobre las dos primeras. La codificación no es cifrado. Cualquiera que tenga el token puede leer todos sus claims, algo que conviene interiorizar antes de meter cualquier cosa en un payload.
Esta herramienta decodifica las dos primeras partes y las formatea, y después extrae los tres claims de tiempo y los convierte de segundos Unix a fechas que puedas leer. No verifica la firma; más abajo está el motivo por el que eso es la decisión correcta y no una función que falta.
Cómo se usa
- Pega el token. Todo lo que va después de
Bearer: el esquema en sí no forma parte del JWT. - Lee la cabecera para ver el algoritmo y, si está, el identificador de clave.
- Lee el payload para ver los claims.
- Revisa la tabla de fechas con
iat,nbf,expy el veredicto de caducidad.
Los claims registrados
| Claim | Nombre | Notas |
|---|---|---|
iss | Emisor | Quién acuñó el token. Tu servidor debería comprobarlo contra una lista de permitidos |
sub | Sujeto | De quién habla el token. Normalmente un id de usuario estable, no un correo |
aud | Audiencia | Para quién es el token. Un token acuñado para otra audiencia hay que rechazarlo |
exp | Caducidad | Segundos Unix. A partir de ahí, rechazar |
nbf | No antes de | Segundos Unix. Antes de ahí, rechazar |
iat | Emitido en | Segundos Unix. Útil para «reautenticar si es más antiguo que N» |
jti | Id del JWT | Id único, para poder revocar un token o comprobar reenvíos |
Los siete son opcionales en la especificación. Un token que omite exp no
caduca nunca por sí solo, y esa es una decisión de diseño que vale la pena
detectar cuando te la encuentras.
Por qué no se ofrece verificación de firma
Verificar una firma necesita la clave: el secreto compartido en HS256, o la
clave pública en RS256 y ES256. En el caso simétrico, la clave que verifica es
también la clave que firma, así que una página que ofreciera verificación te
estaría pidiendo que pegues la credencial que permite a cualquiera acuñar tokens
para tu sistema. No existe ninguna versión de eso que sea segura de construir.
Decodificar sigue mereciendo la pena. La mayor parte de la depuración de JWT
consiste en «qué dice realmente este token»: audiencia equivocada, un scope que
falta, un exp cinco minutos en el pasado, un sub que es un correo cuando tu
código espera un UUID. Todo eso se ve sin la clave.
Lo que decodificar no te puede decir es si el token es auténtico. Un JWT que has recibido es una afirmación, no un hecho, hasta que tu servidor comprueba la firma contra una clave en la que confía.
El campo alg no es una decisión que tu verificador deba delegar
El alg de la cabecera describe lo que usó el emisor. Un verificador que lee
alg y elige su algoritmo en consecuencia está confiando en una entrada
controlada por el atacante, y de ahí salen dos roturas clásicas:
alg: none. El JWT no asegurado es legal en la especificación. Un verificador que lo honra acepta cualquier payload con una firma vacía.RS256cambiado porHS256. Si una biblioteca toma la clave pública y la usa como secreto HMAC, un atacante que conozca tu clave pública —es pública— puede firmar tokens que tú aceptarás.
Las dos son problemas de configuración, no de criptografía. Fija el algoritmo esperado en tu verificador y rechaza cualquier otro, en lugar de leerlo del token.
Ejemplos
- Un 401 que no sabes explicar. Decodifica y mira
aud. Un token acuñado para una API y presentado a otra es la causa más habitual, y el mensaje de error casi nunca lo dice. - Un 401 intermitente. Compara
expconiat. Una vida corta más un cliente que cachea el token es intermitente por construcción. - Un permiso que debería funcionar. Busca
scopeorolesen el payload. Si el claim no está, el problema está en el emisor y no en tu código de autorización. - Auditar lo que metes en un token. Decodifica uno propio. Todo lo que haya ahí —dirección de correo, ids internos, feature flags— lo puede leer quien tenga el token, incluido el navegador donde está guardado.
Dónde debería vivir un token en el navegador
Esto sale cada vez que alguien decodifica un token y se da cuenta de que es legible. La versión corta: como un JWT es una credencial al portador, cualquier cosa que pueda leerlo puede usarlo.
localStorage lo puede leer cualquier script del origen, lo que significa que un
XSS se lleva el token con él. Tampoco se envía nunca de forma automática, así que
por esa vía no te pueden hacer CSRF: cambias una clase de fallo por otra.
Una cookie HttpOnly no la puede leer ningún script, así que un XSS no la puede
robar. Sí se envía automáticamente, así que necesita SameSite=Lax o Strict
más Secure, y cualquier endpoint que cambie estado necesita su propia defensa
contra CSRF.
La conclusión habitual para una aplicación de navegador es un token de acceso de
vida corta guardado solo en memoria, renovado desde una cookie de refresco
HttpOnly, Secure y con SameSite. Así un XSS se lleva un token con minutos
de vida en lugar de una credencial que dura semanas, y el secreto de vida larga
nunca es alcanzable desde JavaScript.
Elijas lo que elijas, el trabajo de verdad lo hace exp. Un token con una vida
de una hora y sin lista de revocación es válido durante una hora después de ser
robado, y nada de lo que hagas en el cliente cambia eso.
Notas
Se añade el relleno antes de decodificar, porque base64url normalmente lo omite. Los tokens copiados de los registros a veces llegan con el relleno intacto, y aquí funcionan las dos formas.
El veredicto de caducidad usa tu reloj local, así que es una comodidad y no una
autoridad. Si necesitas comparar contra un momento concreto, usa el
conversor de tiempo Unix sobre el valor exp en crudo.
Los payloads no siempre son JSON en principio —la especificación permite cualquier tipo de contenido— pero en la práctica todos los JWT llevan un objeto JSON, y un payload que no consigue analizarse como JSON se muestra como texto en crudo en lugar de ocultarse.