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
ktyoalgincompatible; - 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
algque diga el token; - confiar en una
jkuarbitraria; - 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íntoma | Capa probable | Qué revisar |
|---|---|---|
| HS256 invalid signature | secret/encoding | bytes, Base64, newline |
| RS256 invalid signature | public key | issuer, PEM/JWK |
kid not found | selection/cache | JWKS, rotation |
| algoritmo no permitido | policy | allowlist, key type |
| tras editar payload | signed bytes | usar token original |
| firma ok + 401 | claims | exp/iss/aud/type |
Mi workflow determinista
- Conservo el compact JWT original.
- Leo
alg,kideisscon JWT Decoder. - Confirmo algoritmos permitidos por configuración.
- Identifico la fuente de clave confiable para ese issuer.
- En HMAC verifico bytes/encoding exactos.
- En RSA/EC verifico formato, tipo y procedencia.
- Con JWKS reviso
kid, cache y rotation. - Verifico
header.payloadoriginal. - Después valido tiempo, issuer y audience.
- Corrijo key/config/deployment; nunca salto la verificación.
Invalid signature es un fallo de confianza, no un fallo cosmético de parsing.