Mensaje de error
signature verification failed
jose (JavaScript) — JWSSignatureVerificationFailed, code ERR_JWS_SIGNATURE_VERIFICATION_FAILED
Qué significa
jose analizó el JWS correctamente y ejecutó la comprobación criptográfica, pero la firma no se verifica con la clave que seleccionó. La estructura, el algoritmo y el tipo de clave eran aceptables: simplemente, las matemáticas no cuadraron, lo que apunta a la clave o a bytes firmados alterados.
Por qué ocurre
Clave equivocada para este token
frecuenteEl verificador tiene una clave distinta de la que usó el emisor para firmar: un JWKS rotado del que desapareció el kid antiguo, una clave fijada en el código que quedó obsoleta o una confusión de entornos (verificador de preproducción, token de producción).
Los bytes firmados cambiaron entre el firmante y el verificador
frecuenteJWS firma el texto base64url exacto de header.payload. Cualquier cosa que vuelva a codificar esos segmentos —una pasarela que normaliza el relleno de base64, una reserialización del JSON, caracteres recortados en el transporte— rompe la verificación aunque la carga útil decodificada parezca idéntica.
Un firmante ES256 casero produjo DER
ocasionalcrypto.sign() de Node emite ASN.1 DER para ECDSA por defecto, pero el RFC 7518 exige que las firmas JWS ES256 sean r‖s en bruto (exactamente 64 bytes). Un token cuyo tercer segmento se decodifica en unos 70 a 72 bytes que empiezan por 0x30 se firmó con el formato equivocado.
Cómo solucionarlo
- 1.
Resolver las claves por kid desde el JWKS del emisor
Deja que jose elija la clave correcta para cada token en lugar de recurrir a la fijación manual de una sola clave; createRemoteJWKSet guarda las claves en caché y las actualiza a lo largo de las rotaciones.
js import { createRemoteJWKSet, jwtVerify } from "jose"; const jwks = createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")); const { payload } = await jwtVerify(token, jwks, { algorithms: ["ES256"], issuer: "https://issuer.example", }); - 2.
Emitir r‖s en bruto si firmas con node:crypto
En los firmantes personalizados, solicita la salida IEEE P1363 para que la firma tenga el formato de 64 bytes que exige JWS, o verifica lo que produjo tu firmante comprobando la longitud del tercer segmento.
js import { sign } from "node:crypto"; const sig = sign("sha256", Buffer.from(signingInput), { key: privateKey, dsaEncoding: "ieee-p1363", // raw r||s, 64 bytes for P-256 }); - 3.
Comprobar la forma del segmento de firma
Decodifica el tercer segmento del token: 64 bytes significa r‖s en bruto (lo correcto para ES256); unos 70 bytes que empiezan por 0x30 significa DER. El conversor DER ⇄ raw de abajo muestra los valores de r y s y convierte entre los dos formatos.