Cómo decodificar e inspeccionar tokens JWT
Los JSON Web Tokens (JWT) son la manera más común de manejar la autenticación en aplicaciones web modernas. Cuando algo va mal con la autenticación, un usuario se desconecta inesperadamente, los permisos están equivocados, una API devuelve 401, decodificar el JWT suele ser el primer paso de depuración. Entender las tres partes de un JWT, las reclamaciones estándar, los algoritmos con los que puede firmarse, y los errores comunes convierte la depuración de auth de vudú en una comprobación de rutina.
Breve historia del JWT
Los JWT se estandarizaron en la RFC 7519 en mayo de 2015, tras varios años de iteración de borrador en la IETF. El formato tomó prestado de diseños de tokens compactos anteriores (afirmaciones SAML, cookies opacas simples) pero añadió dos cosas que les faltaban: una forma JSON estricta que era legible en cualquier lenguaje, y una codificación base64url-safe que sobrevivía a parámetros de URL, cabeceras HTTP y campos de formulario sin re-escape. Las especificaciones complementarias, JWS (RFC 7515) para firmas, JWE (RFC 7516) para cifrado, y JWA (RFC 7518) para nombres de algoritmo, forman juntas la familia JOSE (JavaScript Object Signing and Encryption).
OAuth 2.0 y OpenID Connect adoptaron JWT como su formato de token por defecto poco después, por lo que casi todos los proveedores de autenticación modernos (Auth0, Okta, Cognito, Keycloak, Firebase, Supabase, Clerk) los emiten hoy. La combinación de tokens autocontenidos y backends sin estado resultó ser un encaje muy natural para microservicios y pasarelas de API. La desventaja es que los JWT son notoriamente fáciles de usar mal, y la última década ha producido un flujo constante de CVE en bibliotecas que no validaron el algoritmo cuidadosamente.
Qué hay dentro de un JWT
Un JWT tiene tres partes, separadas por puntos:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
Cabecera: contiene el algoritmo (HS256, RS256, etc.) y tipo de token.
{"alg": "HS256", "typ": "JWT"}
Payload: contiene claims (afirmaciones de datos) sobre el usuario y el token.
{"sub": "1234567890", "name": "Alice", "exp": 1700000000}
Firma, un hash criptográfico que verifica que el token no ha sido manipulado. No puedes leer esto sin la clave de firma.
Cada sección está codificada en base64url, lo que significa que usa - y _ en lugar de + y / y omite el relleno = final. Base64url no es cifrado; pegar solo el segmento del medio en cualquier decodificador revela el payload. Es por diseño: los segmentos del medio están diseñados para ser legibles por los servicios en el camino, la firma es la única parte que prueba autenticidad.
Claims comunes de JWT
Los claims estándar están registrados con IANA y definidos en la RFC 7519. La mayoría son opcionales, pero los de abajo están casi siempre presentes.
| Claim | Nombre completo | Qué contiene |
|---|---|---|
sub | Subject (sujeto) | ID de usuario o identificador |
exp | Expiration (expiración) | Marca de tiempo Unix cuando expira el token |
iat | Issued At (emitido en) | Marca de tiempo Unix de creación del token |
iss | Issuer (emisor) | Quién creó el token (tu servidor de auth) |
aud | Audience (audiencia) | A quién va dirigido el token |
nbf | Not Before (no antes) | El token no es válido antes de este momento |
jti | JWT ID | Identificador único del token |
azp | Authorized Party | La parte a la que se emitió el token (OIDC) |
scope / scp | Ámbitos OAuth | Permisos concedidos, a menudo separados por espacio |
email | Correo | Identificador estándar OIDC de usuario |
name | Nombre | Nombre de visualización (OIDC) |
nonce | Nonce | Valor OIDC de protección contra reenvío |
kid (cabecera) | Key ID | Qué clave de firma se usó (para consulta JWKS) |
Más allá del conjunto estándar, las aplicaciones añaden sus propios claims personalizados (roles, tenant_id, feature_flags, permissions). Los nombres de claims personalizados no tienen espacio de nombres por defecto, lo que significa que dos servicios distintos pueden usar el mismo nombre para significar cosas distintas; la convención OIDC de prefijar con un URI (https://myapp.com/roles) evita la colisión.
Cómo decodificar un JWT
- Pega tu token: introduce el JWT completo (formato header.payload.signature) en el decodificador. Los decodificadores basados en navegador lo procesan localmente, el token nunca sale de la página.
- Mira las secciones decodificadas: la herramienta muestra la cabecera (algoritmo), payload (claims), y firma como JSON formateado, con marcas de tiempo mostradas tanto como enteros Unix como fechas legibles.
- Comprueba los claims: examina el tiempo de expiración, emisor, sujeto, audiencia, y cualquier claim personalizado que conduzca tu lógica de autorización.
- Compara con expectativas: cruza el emisor con el proveedor de auth que configuraste, la audiencia con la API a la que se envía el token, y cualquier claim de rol/ámbito con los permisos que el usuario debería tener.
- Prueba de viaje en el tiempo: pasa el ratón sobre
iat,nbf, yexppara ver si el token es válido actualmente, va a expirar pronto, o se emitió hace tanto tiempo que tu tolerancia de desfase de reloj ya no lo cubre.
Algoritmos de firma
No todos los JWT usan la misma cripto. La cabecera alg te dice a qué familia pertenece la firma, y cada una tiene propiedades de seguridad muy distintas.
| Algoritmo | Familia | Tipo de clave | Cuándo elegir |
|---|---|---|---|
HS256 | HMAC | Secreto compartido | Apps de un solo servicio; nunca compartas el secreto entre equipos |
HS384 / HS512 | HMAC | Secreto compartido | Como HS256 con digests más largos |
RS256 | RSA | Par de claves pública/privada | Lo más común para OIDC; los verificadores solo necesitan la clave pública |
RS384 / RS512 | RSA | Par de claves | Como RS256 con claves más grandes |
PS256 / PS384 / PS512 | RSA-PSS | Par de claves | RSA moderno, recomendado sobre RS para despliegues nuevos |
ES256 / ES384 / ES512 | ECDSA | Par de claves de curva elíptica | Claves más pequeñas que RSA, verificación más rápida |
EdDSA | Ed25519 | Par de claves curva de Edwards | El más nuevo, pequeño, rápido; aún no universal |
none | Ninguno | Ninguna | Prohibido en producción; algunas bibliotecas antiguas todavía lo aceptan |
Los algoritmos asimétricos (RS*, PS*, ES*, EdDSA) permiten a cualquier servicio verificar un token solo con una clave pública, por lo que dominan OIDC. Simétrico (HS*) está bien dentro de una sola aplicación pero se vuelve una pesadilla para rotar o distribuir entre múltiples consumidores.
Depuración con JWT
¿Token expirado? Comprueba el claim exp. Convierte la marca de tiempo Unix en una fecha legible. Si está en el pasado, el token ha expirado y necesita refrescarse. La mayoría de bibliotecas JWT rechazan tokens expirados por defecto; si tu app los acepta, es un bug de seguridad.
¿Permisos equivocados? Busca claims de rol o ámbito en el payload. Varían por implementación pero suelen parecer "role": "admin" o "scope": "read write profile".
¿Problemas de identidad de usuario? El claim sub identifica al usuario. Verifica que coincida con el ID de usuario esperado. Nota que algunos proveedores usan GUIDs opacos mientras otros usan direcciones de correo; el decodificador te muestra exactamente lo que hay.
¿Token no aceptado? Comprueba el claim aud (audiencia). Si la API espera un valor de audiencia específico y el token tiene uno distinto, se rechazará. Los desajustes de audiencia son un síntoma común de enrutar un token al servicio equivocado.
¿Errores 401 tras desplegar? Comprueba el claim iss (emisor). Un nuevo tenant de proveedor de auth o una clave de firma cambiada modifica la URL del emisor; si tu verificador aún confía en la vieja, todos los tokens parecen inválidos.
¿Problemas de desfase de reloj? Si iat está ligeramente en el futuro o exp ligeramente en el pasado, el reloj de tu servidor puede estar derivando. La mayoría de bibliotecas JWT permiten unos segundos de margen; si no, un reloj sincronizado por NTP arregla el problema.
Errores comunes
- Confiar en la cabecera
algsin lista blanca, la vulnerabilidad JWT clásica era un servidor aceptando el algoritmo que el token decía usar. Un token conalg: none(sin firma) oalg: HS256(firmado con tu clave pública como secreto) podía forjar cualquier payload. Fija el verificador al algoritmo exacto esperado. - Poner secretos en el payload, el payload está codificado en base64url, no cifrado. Cualquiera con el token puede leerlo. Nunca incluyas contraseñas, claves API, ni nada que no pondrías en una cadena de consulta.
- Tokens de larga vida sin revocación, un JWT de 30 días no puede revocarse sin lista negra de tokens o almacén de sesión. Mantén los tokens de acceso cortos (5-60 minutos) y usa un flujo de token de refresco para sesiones más largas.
- Olvidar el desfase de reloj, los servidores en distintas zonas horarias o con relojes derivados rechazan tokens que deberían ser válidos. Permite 30-60 segundos de margen en
expynbf. - Confiar en el emisor sin verificación, el claim
isses parte del payload que cualquiera puede escribir. Verificar que su valor coincida con el emisor configurado es requerido; ponerlo en lista blanca previene que un atacante acuñe tokens que pretenden venir de tu proveedor. - Reutilizar secretos HS256 entre entornos, el mismo secreto en dev y prod significa que un token de dev filtrado funciona en producción. Usa claves por entorno, idealmente obtenidas de un gestor de secretos.
- Guardar tokens en localStorage, localStorage es legible por cualquier JavaScript, así que un solo bug XSS exfiltra los tokens de todos los usuarios. Las cookies HttpOnly con SameSite=Lax (o Strict) son el valor por defecto más seguro.
- Registrar tokens completos, los logs de aplicación que capturan JWT completos filtran los tokens a cualquiera con acceso a logs. Trunca a los primeros 10 caracteres o registra solo el
jti. - Ignorar la rotación de
kid, cuando un proveedor rota claves de firma, los nuevos tokens tienen una nueva cabecerakid. Un verificador que cachea el JWKS para siempre empezará a rechazar tokens válidos. Re-busca el JWKS en fallo de key-id. - Mezclar JWT con sesiones inconsistentemente, algunos endpoints detrás de JWT, otros detrás de sesiones cookie, lleva a bugs donde un usuario conectado parece no autenticado en una ruta. Elige un modelo por servicio.
Alternativas a JWT
JWT es dominante pero no la única opción. Cada alternativa intercambia propiedades distintas.
| Mecanismo | Fortaleza | Debilidad |
|---|---|---|
| JWT (JWS) | Autocontenido, fácil entre servicios | No se puede revocar sin estado extra |
| Tokens opacos + introspección | Fácil de revocar, oculta claims | Cada petición toca el servidor de auth |
| Sesiones del lado del servidor | Modelo más simple, revocación instantánea | Difícil escalar entre servicios |
| PASETO | Reemplazo más seguro de JWT (sin confusión alg) | Ecosistema más pequeño |
| Macaroons | Atenuación incorporada (derechos delegados) | Soporte de bibliotecas limitado |
| OAuth 2.0 + tokens de acceso JWT | Estándar de la industria para APIs | Especificación grande, fácil de mal implementar |
| Tokens ID OIDC | Identidad de usuario estándar + JWT | A menudo confundidos con tokens de acceso |
| Certificados de cliente mTLS | La auth más fuerte en la capa de transporte | Sobrecarga de gestión de certificados |
Para la mayoría de equipos, la elección está entre JWT y tokens opacos. JWT gana cuando la verificación necesita ser barata y offline; los tokens opacos ganan cuando la revocación tiene que ser instantánea.
Privacidad y el decodificador
El decodificador JWT corre enteramente en tu navegador. El token que pegas se divide, se decodifica en base64url, y el JSON se parsea y se imprime con formato sin ninguna petición de red. No hay registro de los tokens que se han decodificado, ni analítica sobre los claims que contienen, ni manera de que nadie reconstruya para quién estabas depurando. Los JWT contienen a menudo identificadores de usuario, direcciones de correo, nombres de roles internos, e IDs de tenant, exactamente el tipo de metadatos que no quieres enviar al servidor de un extraño. Decodificar del lado del cliente mantiene esa información en tu máquina, que es el valor por defecto correcto para cualquier tarea de depuración que toque autenticación.
Preguntas frecuentes
¿Puedo verificar una firma JWT con un decodificador?
No. La verificación de firma requiere el secreto de firma o la clave pública, que se guardan en tu servidor. Un decodificador te muestra lo que hay en el token, pero la verificación criptográfica debe hacerse en tu backend. Nunca confíes en un JWT no verificado en producción.
¿Es seguro pegar un JWT en una herramienta en línea?
Sí, cuando la herramienta se ejecuta en tu navegador. Los decodificadores en el navegador procesan el token localmente, nada se envía a un servidor. Evita las herramientas que hacen peticiones de red con tu token.
¿Qué es la reclamación exp?
La reclamación exp (expiration) es una marca de tiempo Unix que indica cuándo expira el token. Tras esa fecha, el token debe rechazarse. Comprueba siempre esta reclamación para depurar problemas de autenticación.
¿Pueden cifrarse los JWT?
Los JWT estándar (JWS) están firmados pero no cifrados, cualquiera puede decodificar la carga útil. Los tokens JWE (JSON Web Encryption) están cifrados, pero son menos habituales. Nunca pongas datos sensibles (contraseñas, secretos) en una carga útil JWT estándar.
What is the alg none vulnerability?
Early JWT libraries accepted tokens with an alg header set to "none", meaning the signature could be omitted entirely. An attacker who set this header could forge any payload. Modern libraries reject "none" by default, but legacy systems may still be exposed; always allow-list the expected algorithm rather than trusting the header.
How should I store a JWT on the client?
HttpOnly secure cookies with SameSite=Lax (or Strict) are the safest default; they cannot be read by JavaScript, which mitigates XSS token theft. localStorage is convenient but vulnerable to any XSS bug. Never store long-lived JWTs alongside untrusted scripts.