Guías JWT

Cómo depuro una firma JWT inválida

Mi orden para claves incorrectas, problemas de algoritmo, errores de kid, rotación, encoding HMAC y bytes del token modificados sin desactivar la verificación.

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

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

Flujo para depurar una firma JWT inválida
Confirmo algoritmo, issuer, kid y selección de clave antes de cambiar la verificación.

Ante invalid signature o signature verification failed no edito el JWT ni pruebo opciones hasta que algo pase. Conservo el compact token original y separo la investigación por algoritmo, bytes de clave, issuer, kid, JWKS y rotation.

Con secret/public key conocido uso JWT Signature Verifier; con JWKS uso JWKS Validator.

Qué significa exactamente invalid signature

Un JWS compacto firma:

base64url(header).base64url(payload)

La firma se comprueba contra esos bytes originales. Si falla, al menos uno de algoritmo, key material o signed bytes no coincide con el signer.

No significa automáticamente expired token, invalid audience o falta de permisos.

Primero separo HMAC de RSA/EC

{ "alg": "HS256" }

vs.

{ "alg": "RS256", "kid": "prod-2026-09" }

Mi árbol empieza así:

HS* -> secret bytes / encoding / rotation
RS*/ES* -> public key / kid / JWKS / issuer

La allowlist de algoritmos viene de la aplicación, no del header no confiable.

HMAC: el problema suele estar en los bytes

Reviso:

  • UTF-8 raw vs Base64-decoded secret;
  • newline al final de env var;
  • secrets distintos por environment;
  • secret rotation;
  • espacios de copy/paste;
  • secrets demasiado débiles.
const utf8 = new TextEncoder().encode(process.env.JWT_SECRET);
const b64 = Uint8Array.from(atob(process.env.JWT_SECRET), c => c.charCodeAt(0));

Esos arrays pueden representar claves distintas aunque partan del mismo texto visible.

RSA: compruebo formato y procedencia de la clave

-----BEGIN PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
-----BEGIN RSA PUBLIC KEY-----

No todas las librerías aceptan SPKI, X.509 y PKCS#1 de la misma forma. Convierto explícitamente; no elimino encabezados al azar.

kid, JWKS y rotation

Si hay kid, lo busco solo en el JWKS confiable del issuer esperado. Fallos frecuentes:

  • caché JWKS desactualizada;
  • new key aún no cargada;
  • old key retirada demasiado pronto;
  • issuer/tenant equivocado;
  • match con kty o alg incompatible;
  • duplicate kid.

Para esto consulto JWT kid y key rotation.

Mensajes que separo por fase

JsonWebTokenError: invalid signature
JWSSignatureVerificationFailed
signature verification failed
kid not found
no applicable key found in the JSON Web Key Set

kid not found es key selection; invalid signature implica que sí se llegó a la comparación criptográfica. Distinguirlos ahorra tiempo.

No reserializo el payload antes de verificar

Estas dos representaciones pueden parecer equivalentes:

{"sub":"42","role":"user"}
{ "role": "user", "sub": "42" }

pero producen bytes distintos. JWS firma el input compacto original, no el objeto JSON «equivalente».

Firma válida no significa token aceptable

Después aún valido:

  • exp/nbf;
  • expected iss;
  • expected aud;
  • token type;
  • scopes/roles.

Para ese conjunto uso JWT Validator o JWT Debugger.

Lo que no uso como solución

  • desactivar signature verification;
  • aceptar el alg que diga el token;
  • confiar en una jku arbitraria;
  • editar payload y mantener firma vieja;
  • tomar el primer key del JWKS;
  • sustituir un secret fuerte por una contraseña corta.

Matriz de diagnóstico

SíntomaCapa probableQué revisar
HS256 invalid signaturesecret/encodingbytes, Base64, newline
RS256 invalid signaturepublic keyissuer, PEM/JWK
kid not foundselection/cacheJWKS, rotation
algoritmo no permitidopolicyallowlist, key type
tras editar payloadsigned bytesusar token original
firma ok + 401claimsexp/iss/aud/type

Mi workflow determinista

  1. Conservo el compact JWT original.
  2. Leo alg, kid e iss con JWT Decoder.
  3. Confirmo algoritmos permitidos por configuración.
  4. Identifico la fuente de clave confiable para ese issuer.
  5. En HMAC verifico bytes/encoding exactos.
  6. En RSA/EC verifico formato, tipo y procedencia.
  7. Con JWKS reviso kid, cache y rotation.
  8. Verifico header.payload original.
  9. Después valido tiempo, issuer y audience.
  10. Corrijo key/config/deployment; nunca salto la verificación.

Invalid signature es un fallo de confianza, no un fallo cosmético de parsing.

Fuentes y referencias

Guías relacionadas