Guías JWT

Cómo decodifico un JWT sin secreto y sin confiar en él

Explico por qué puedo leer header y payload de un JWT firmado sin secreto, qué reviso primero y en qué momento necesito una clave confiable para verificar la firma.

Por JWTDecoder.toolsÚltima verificación 2026-09-13

Voz editorial: la primera persona describe el flujo de trabajo documentado de JWTDecoder.tools; JWTDecoder.tools es el editor y autor organizacional.

Flujo para decodificar JWT sin secret antes de verificar la firma
Primero decodifico e inspecciono; después uso una clave confiable para decidir autenticidad.

Cuando recibo un JWT y mi primera pregunta es simplemente “¿qué contiene este token?”, no empiezo buscando un secreto de firma. Primero compruebo si estoy ante un JWT/JWS firmado de tres segmentos o un JWE cifrado. En el formato firmado habitual puedo leer header y payload sin conocer ningún secreto.

La distinción que mantengo durante todo el diagnóstico es esta: decodificar me dice lo que el token afirma; verificar me dice si una clave que ya confío firmó exactamente esos bytes; validar me dice si mi aplicación debe aceptarlo.

Si ya tengo el token, suelo empezar con el JWT Decoder para inspeccionar header, payload, tiempos y el segmento de firma localmente.

Primero compruebo si es JWS de 3 segmentos o JWE de 5

Un JWT firmado suele verse así:

header.payload.signature

Un JWE compacto normalmente tiene cinco segmentos. Si veo cinco, dejo de esperar que Base64URL revele los claims: ahí estoy ante cifrado y necesito decryption, no simple decoding.

Esta comprobación evita que trate cualquier cadena que empieza por eyJ como si fuera el mismo tipo de token.

Por qué puedo leer un JWT firmado sin secreto

Header y payload son JSON codificado con Base64URL. Base64URL es encoding, no encryption.

Por ejemplo, puedo obtener:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-2026-09"
}

Y un payload como:

{
  "sub": "user_123",
  "iss": "https://auth.example.com/",
  "aud": "api://backend",
  "scope": "read:user write:profile",
  "iat": 1789000000,
  "exp": 1789003600
}

No necesité una clave para leer esos objetos. La firma protege integridad y autenticidad; no oculta los claims de un JWT firmado normal.

Qué miro primero después de decodificar

No leo todos los campos con el mismo peso. Normalmente reviso:

  1. alg — algoritmo declarado.
  2. kid — identificador que puede ayudar a seleccionar una clave.
  3. iss — issuer que el token afirma tener.
  4. aud — destinatario o API prevista.
  5. exp, nbf, iat — ventana temporal.
  6. scope, roles, groups — datos de autorización.
  7. sub — sujeto representado.

La guía de claims JWT profundiza en cada uno. En esta fase los trato como datos no confiables.

Por qué no confío en un payload legible

Cualquiera puede construir JSON, codificarlo en Base64URL y fabricar una cadena con aspecto de JWT.

Puedo cambiar:

{ "role": "user" }

por:

{ "role": "admin" }

y seguir decodificando el resultado. Lo que un atacante no debería poder producir sin la credencial de firma autorizada es una nueva firma que un verificador correctamente configurado acepte.

Por eso nunca traduzco “se pudo decodificar” como “JWT válido”.

Cuándo necesito el secreto o la clave pública

Cambio de decoding a verification cuando mi pregunta pasa de:

¿Qué dice el token?

A:

¿El firmante confiable produjo exactamente estos bytes?

Para HS256 necesito el secreto compartido correcto. Para RS256 normalmente necesito la clave pública del issuer, no su private key.

Si tengo material confiable uso el JWT Signature Verifier. Si la infraestructura publica JWKS, uso JWKS Validator para seleccionar la clave correcta mediante kid.

Separo decode, verify y validate

Decode

Leo header, payload, timestamps y la presencia de signature.

Verify

Compruebo criptográficamente la firma usando una clave cuya procedencia ya confío.

Validate

Compruebo iss, aud, exp, nbf, token type, scopes, roles y policy de mi aplicación.

Este orden evita convertir un Base64 decode exitoso en una conclusión de seguridad.

Si parece un problema de expiración

exp, iat y nbf usan NumericDate en segundos. No los comparo directamente con Date.now() en JavaScript sin convertir unidades.

Para revisar la línea temporal uso el JWT Expiry Checker. Si exp todavía está en el futuro pero la API devuelve 401, continúo con issuer, audience y firma.

Cómo trato bearer tokens de producción

Que el payload sea legible no hace que un bearer token deje de ser una credencial.

Mis reglas habituales son:

  • no pongo JWT en URLs;
  • no registro bearer tokens completos;
  • prefiero tokens sintéticos o redactados para reproducir errores;
  • no pego refresh tokens, private keys ni secretos HMAC en reportes;
  • no envío un token a un servidor solo para decodificar Base64URL.

JWTDecoder.tools mantiene el decoding en memoria del navegador, pero ningún sitio web puede proteger un token frente a malware, extensiones o herramientas de clipboard ya presentes en el dispositivo.

Mi checklist final

  1. Confirmo 3 segmentos JWS o 5 segmentos JWE.
  2. Decodifico header y payload como Base64URL/UTF-8 JSON.
  3. Reviso alg, kid, iss, aud y claims de tiempo.
  4. Mantengo todos los claims como no confiables hasta verificar firma.
  5. Si sospecho tiempo, uso JWT Expiry Checker.
  6. Si necesito autenticidad, uso JWT Signature Verifier o JWKS Validator.
  7. Si la API sigue rechazando, comparo issuer y audience en JWT Debugger.

La regla que uso en la práctica es sencilla: no necesito un secreto para leer un JWT firmado normal, pero sí necesito una clave confiable antes de creer que esos claims son auténticos.

Fuentes y referencias

Guías relacionadas