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:
alg— algoritmo declarado.kid— identificador que puede ayudar a seleccionar una clave.iss— issuer que el token afirma tener.aud— destinatario o API prevista.exp,nbf,iat— ventana temporal.scope,roles,groups— datos de autorización.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
- Confirmo 3 segmentos JWS o 5 segmentos JWE.
- Decodifico header y payload como Base64URL/UTF-8 JSON.
- Reviso
alg,kid,iss,audy claims de tiempo. - Mantengo todos los claims como no confiables hasta verificar firma.
- Si sospecho tiempo, uso JWT Expiry Checker.
- Si necesito autenticidad, uso JWT Signature Verifier o JWKS Validator.
- 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.