Verificar la firma de un JWT con un JWKS sin backend

Verificar la firma de un JWT con un JWKS sin backend

Pega un token y un JWKS para comprobar una firma RS256, ES256 o HS256 en el navegador. Elección de la clave por kid, errores medidos y claims que revisar.

26.10.2026
14 min de lectura
Compartir este artículo:
JWTjwksoidcSeguridadTutorial

Comprobar una firma exige el token y la clave pública correcta

Decodificar un JWT muestra lo que afirma. Verificarlo muestra si la clave esperada firmó esas afirmaciones. Con un proveedor de OpenID Connect como Auth0, Keycloak o Cognito, esa clave está en un JWKS, un documento JSON de claves públicas. Esta guía explica cómo el verificador elige una clave y cómo leer su respuesta cuando una integración falla. Usa la herramienta JWT verify, que toma el JWKS como texto pegado y nunca abre una dirección, así que no hace falta un servidor. El 26 de octubre de 2026 pasamos 31 casos de prueba por el código del verificador y contamos qué devolvió cada uno. Incluyen tokens RS256, ES256 y HS256, además de tokens alterados, caducados o mal emparejados. Las reglas salen de las RFC 7515, 7517, 7518, 7519 y 8725. Si antes quieres leer un token, empieza por nuestra guía para codificar y decodificar JWT.

Decodificar lee un token. Verificar comprueba su firma con una clave pública del JWKS.
La cabecera kid señala la clave. Es una pista y es opcional (RFC 7515, sección 4.1.4).
Un token sin kid se verificó con un JWKS de una sola clave y falló con un JWKS de tres claves.
Una firma RS256 de una clave de 2048 bits mide 256 bytes, y una firma ES256 mide 64.
Una firma válida no dice que el token sea para tu API. Revisa también iss, aud y exp.
Pega el cuerpo del JWKS, no su dirección. La herramienta no descarga nada.

Cómo funciona un JWKS y cómo se elige la clave

Un JWKS es una lista JSON de claves públicas

La RFC 7517 define un JWK Set como un objeto con un arreglo keys. El orden de las claves no expresa preferencia, salvo que la aplicación decida otra cosa. Cada clave indica su tipo en kty y lleva los números necesarios para comprobar una firma.

Mejoras

Las claves RSA llevan el módulo n y el exponente e. En nuestra prueba, el valor n de una clave de 2048 bits tenía 342 caracteres Base64URL.
Las claves EC llevan la curva crv y las coordenadas x y y. Una clave P-256 produce firmas ES256.
kid, alg y use son etiquetas opcionales. use vale sig para firmar o enc para cifrar.
Nuestro conjunto de prueba (dos claves RSA y una EC, cada una con kid, alg y use) ocupaba 997 caracteres, así que un JWKS pegado sigue siendo pequeño.
Una clave de tipo oct guarda un secreto compartido en k. Es material privado, incluso dentro de un documento llamado conjunto de claves.
El kid elige la clave, y solo es una pista

La RFC 7515 llama a kid una pista sobre la clave que protegió el token, y la cabecera es opcional. El verificador la compara con el kid de cada JWK. Lo que ocurre sin coincidencia depende de la herramienta, así que lo probamos.

Mejoras

Token con kid k1 y un conjunto de tres claves que incluye k1: verificado, y el resultado nombra k1.
Token sin kid y un conjunto de tres claves: ninguna clave coincide, porque el verificador no puede adivinar.
Token sin kid y un conjunto de una clave, o un JWK suelto: verificado.
Token con kid a y una sola clave sin kid: ninguna clave coincide. Una etiqueta en un solo lado no basta.
Token con un kid desconocido: ninguna clave coincide. En producción suele indicar que el proveedor rotó sus claves.
Tamaños y formatos de las firmas

El tercer segmento del token es la firma. Su longitud y su codificación dependen del algoritmo, lo que explica algunos fallos que parecen corrupción.

Mejoras

RS256 con una clave de 2048 bits dio una firma de 256 bytes, por eso el tercer segmento de un token RSA es largo.
ES256 dio 64 bytes. La RFC 7518 la define como R y S, de 32 bytes cada uno, uno tras otro.
La RFC 7518 exige un valor de 64 octetos para ES256, así que una firma DER como la que imprime OpenSSL no es una firma JWS válida hasta convertirla.
Las firmas HS256 miden 32 bytes, y verificarlas exige el mismo secreto compartido que las produjo.
Lo que demuestra una firma válida y lo que no

Una firma válida muestra que quien tiene la clave privada firmó esta cabecera y este payload. No muestra que el token sea para tu servicio ni que siga vigente.

Mejoras

Cambiamos sub por admin en un token válido. El veredicto fue firma inválida.
La RFC 8725, sección 3.8, exige que las claves usadas pertenezcan al emisor nombrado en iss.
En nuestra prueba, un aud con valor other y un iss con https://evil.example fallaron como errores de claims, aunque la firma era correcta.
Un token caducado hacía 30 segundos pasó con una tolerancia de reloj de 60 segundos y falló con una tolerancia de 0.

Depurar un token OIDC sin servidor

Reunir el token y el cuerpo del JWKS

Copia el token de acceso o de identidad de la petición que falla, sin el prefijo Bearer. Después consigue el JWKS del proveedor. Su dirección es el valor jwks_uri del documento de descubrimiento, que se sirve en la dirección del emisor seguida de /.well-known/openid-configuration.

Auth0 publica el conjunto en https://TU_DOMINIO/.well-known/jwks.json.
Cognito usa https://cognito-idp.REGION.amazonaws.com/ID_DEL_POOL/.well-known/jwks.json y firma sus tokens con RS256.
Keycloak usa /realms/REALM/protocol/openid-connect/certs. Las versiones recientes ya no añaden el antiguo prefijo /auth.
Abre la dirección en un navegador o con curl, copia el cuerpo JSON y pega ese cuerpo, no la dirección.
Lee el emisor en el claim iss del token y toma la dirección de su documento de descubrimiento en lugar de escribirla a mano.
Fijar lo que tu servidor debe comprobar

Las mismas decisiones valen en la herramienta y en el código de producción. Fíjalas una vez, en tu configuración, y nunca las leas del token.

Acepta solo los algoritmos que esperas, por ejemplo RS256. La RFC 8725, sección 3.1, deja la lista de algoritmos en manos de la aplicación.
Compara iss con la dirección exacta del emisor.
Compara aud con el identificador de tu API.
Admite una pequeña tolerancia de reloj. La RFC 7519 habla de unos pocos minutos como máximo, y la herramienta ofrece 0, 60 o 300 segundos.
Recarga el JWKS en caché cuando un token lleve un kid que no conoces.

Leer la respuesta del verificador

Ninguna clave coincide

La herramienta responde key_not_found cuando el token no tiene kid y el conjunto contiene varias claves, o cuando su kid no está en el conjunto.

Decodifica el token en el decodificador y compara el kid de su cabecera con los kid del conjunto pegado. Las mayúsculas cuentan.
Con varias claves y sin kid en el token, el verificador no puede elegir. Pega solo la clave esperada o pide al emisor que añada kid.
Un kid desconocido sugiere que el proveedor rotó sus claves. La documentación de Cognito indica entonces recargar las claves en caché.
Un conjunto copiado antes de la rotación no tiene la clave nueva. Copia el cuerpo otra vez desde el proveedor.
Firma inválida

La herramienta encontró una clave y la comprobación falló. Varias causas dan el mismo mensaje.

Un payload alterado falla. Cambiar sub en un token válido dio firma inválida.
Un token cuyo kid apunta a la clave B, pero que firmó la clave A, falla igual. Busca dos entornos, como preproducción y producción, con conjuntos distintos.
Un JWK privado dio firma inválida, con un mensaje que exige una clave pública. Pega la parte pública y revisa antes el conjunto en el inspector.
Una clave de un tipo distinto al del algoritmo, por ejemplo una clave EC con un token RS256, también acaba aquí.
La herramienta rechaza la entrada

Algunas respuestas llegan antes de cualquier comprobación de firma.

Estructura JWT inválida: el token conserva su prefijo Bearer. En nuestra prueba la herramienta no lo quitó. Un salto de línea al final no molestó.
JSON de JWK o JWKS inválido: una dirección pegada en lugar del cuerpo, una copia cortada o un arreglo keys vacío.
Algoritmo no admitido en alg none: un token sin firma no se puede verificar, y tu servidor también debe rechazarlo.
Secreto requerido: el token dice HS256 y el conjunto solo tiene claves RSA públicas. HMAC necesita un secreto compartido o una clave oct.

Cuatro errores al verificar con un JWKS

Tomar una firma válida por un permiso

La firma demuestra quién emitió el token. No dice nada sobre lo que su portador puede hacer en tu servicio.

Aceptar cualquier token firmado por el proveedor, sea cual sea su audiencia.
Saltarse la comprobación de iss porque el JWKS ya viene del emisor.
Leer el resultado verde como una autorización.
Un token emitido para otra API del mismo proveedor pasa la comprobación de firma y se acepta.
Verifica juntos la firma, iss, aud y exp, y aplica después tus propias reglas de acceso.
Pegar material de una clave privada

Un JWK con d, o una clave oct con k, es un secreto. Un conjunto de claves públicas nunca lo necesita.

Pegar en un chat, un ticket o una herramienta en línea un JWK que aún contiene d.
Copiar un conjunto de un almacén de secretos en lugar del punto de acceso público.
Subir al repositorio un JWKS de prueba que conserva sus campos privados.
Nuestra prueba con un JWK RSA privado falló solo porque la biblioteca quería una clave pública. El mismo pegado en otro sitio podría filtrar d.
Abre primero el conjunto en el inspector de JWKS. Su vista Inventario indica hasPrivate por clave, y la vista formateada quita d, los factores RSA y la k de las claves oct.
Dejar que el token elija el algoritmo

La cabecera alg pertenece al remitente. Si el verificador la sigue, es el atacante quien elige la comprobación.

No pasar ninguna opción algorithms y fiarse de la cabecera alg.
Usar una clave pública RSA como secreto de un token HS256.
Aceptar alg none en una ruta de prueba que llega a producción.
El verificador de FastMinify toma el alg del token cuando pegas una sola clave. En nuestra prueba, una clave etiquetada RS512 verificó un token RS256, y una marcada use enc también. El código de producción debe ser más estricto.
Fija la lista de algoritmos en tu configuración y usa una biblioteca que compare alg, use y key_ops, como hace jose.
Descargar el JWKS en cada petición, o no volver a hacerlo

Los dos extremos fallan. Uno añade una llamada de red a cada petición, el otro deja fuera a los usuarios en la primera rotación.

Llamar al punto de acceso del JWKS por cada token.
Guardar el conjunto en caché para siempre.
Escribir una clave a mano en lugar del conjunto.
jose guarda las claves 10 minutos por defecto y solo vuelve a leer el conjunto ante un kid desconocido pasados 30 segundos de espera.
Usa una biblioteca que guarde en caché y recargue ante un kid desconocido, y ajusta sus límites a propósito.

Cuatro ideas falsas sobre JWKS y firmas

El kid demuestra qué clave firmó el token

La etiqueta la escribe el remitente, así que por sí sola no demuestra nada.

Falso. La RFC 7515 habla de una pista. En nuestra prueba, un token con kid k2 firmado por la clave k1 devolvió firma inválida.
Un JWKS es un documento secreto

Mucha gente duda antes de pegar uno en una herramienta.

Un conjunto de claves públicas es público por diseño y cualquiera puede descargarlo. La excepción es la clave oct, cuyo valor k es un secreto compartido que nunca debes publicar.
Las firmas ES256 son ASN.1 DER, como las de OpenSSL

Esta creencia lleva a escribir código de conversión que un JWT no necesita.

Falso. La RFC 7518 exige un valor de 64 octetos formado por R y S. Nuestro token ES256 de prueba tenía una firma de 64 bytes.
Verificar un JWT exige un backend

La idea viene de las bibliotecas que descargan las claves y las guardan en caché en un servidor.

No. Un JWKS son datos públicos y la verificación es un cálculo local. La página de verificación trabaja con texto pegado en tu navegador y no descarga claves.

Verificar e inspeccionar en el navegador

Verificar un token con un JWKS

La herramienta JWT verify comprueba la firma y, si lo pides, la audiencia, el emisor y los claims de tiempo. Admite 14 algoritmos, entre ellos RS256, ES256 y HS256.

1

Pega el token

Pega primero el JWT compacto. Las pestañas de clave siguen el alg leído en su cabecera.

2

Pega el cuerpo del JWKS

Pega el JSON del conjunto de claves, un JWK suelto o un PEM público. Para convertir antes una clave PEM en JWK, usa el conversor de PEM y JWK. Para un token HS256, usa la pestaña Secreto HMAC.

3

Ajusta las comprobaciones

Escribe la audiencia y el emisor si quieres compararlos, y elige una tolerancia de reloj de 0, 60 o 300 segundos. La opción Ignorar caducidad sigue comprobando la firma.

4

Lee el veredicto

El resultado muestra válido con el algoritmo y el kid, o un código de error descrito en la sección de resolución de problemas de arriba.

Revisar antes el conjunto de claves

El inspector de JWKS lee un JWK o un JWKS sin verificar nada. Úsalo antes del verificador cuando el conjunto venga de otra persona.

1

Pega el JWKS

Pega un conjunto de claves o una clave suelta. Los campos privados se quitan de la salida y de la copia.

2

Cambia a la vista Inventario

El inventario lista kid, kty, use, alg, crv y hasPrivate por clave, sin mostrar material de clave.

3

Ordena las claves

La opción Ordenar claves ordena el JSON para comparar dos entornos con un diff.

Lo que las herramientas no hacen

Las dos páginas trabajan con texto pegado. Conviene conocer algunos límites.

No descargan una dirección de JWKS: pegas el cuerpo.
No admiten tokens cifrados (JWE): solo firmados, de tres partes.
La herramienta lee el algoritmo del token y no lo compara con el alg de una clave suelta.
Verificado no significa fiable: la herramienta no sabe si el emisor merece tu confianza.
Cada campo está limitado a 512 KB.

Las mismas comprobaciones en un terminal y en tu código

Listar las claves de un proveedor con curl y jq

Sustituye la dirección por la de tu emisor. El último comando lee el kid del primer segmento de un token guardado en la variable TOKEN.

Ejemplo básico

# Leer el jwks_uri del documento de descubrimiento curl -s https://auth.example.com/.well-known/openid-configuration | jq -r .jwks_uri # Listar las claves: kid, tipo, algoritmo y uso curl -s https://auth.example.com/.well-known/jwks.json | jq -c '.keys[] | {kid, kty, alg, use}' # Leer el kid en la cabecera del token echo "$TOKEN" | jq -R 'split(".")[0] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson | .kid'
Verificar con jose y un JWKS remoto

createRemoteJWKSet descarga el conjunto, lo guarda en caché y elige la clave por kid y alg. El algoritmo, el emisor y la audiencia salen de tu configuración.

Ejemplo básico

import { createRemoteJWKSet, jwtVerify } from 'jose' const jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json')) const { payload, protectedHeader } = await jwtVerify(token, jwks, { algorithms: ['RS256'], // lo fijas tú, nunca se lee del token issuer: 'https://auth.example.com', audience: 'orders-api', clockTolerance: 60, // tolerancia, como número de segundos }) console.log(protectedHeader.kid, payload.sub)
Verificar con un JWKS pegado, como hace la herramienta

createLocalJWKSet toma el conjunto que ya tienes. En nuestras pruebas lanzó ERR_JWKS_NO_MATCHING_KEY ante un kid desconocido. Lanzó ERR_JWKS_MULTIPLE_MATCHING_KEYS ante un token sin kid y dos claves RSA, y ERR_JOSE_ALG_NOT_ALLOWED cuando la lista de algoritmos excluía el alg del token.

Ejemplo básico

import { createLocalJWKSet, jwtVerify } from 'jose' // jwksText es el cuerpo JSON que copiaste del proveedor const jwks = createLocalJWKSet(JSON.parse(jwksText)) try { const { payload, protectedHeader } = await jwtVerify(token, jwks, { algorithms: ['RS256'], issuer: 'https://auth.example.com', audience: 'orders-api', clockTolerance: 60, }) console.log(protectedHeader.kid, payload.sub) } catch (err) { // ERR_JWKS_NO_MATCHING_KEY, ERR_JWS_SIGNATURE_VERIFICATION_FAILED, ERR_JWT_CLAIM_VALIDATION_FAILED, ERR_JWT_EXPIRED console.error(err.code) }

Conclusión

Verificar con un JWKS se reduce a tres decisiones. Elige la clave por kid y alg. Comprueba iss, aud y exp. Si falta la clave, recarga el conjunto en lugar de confiar en el token. Las herramientas del navegador permiten reproducir cada caso con texto pegado antes de tocar un servidor. Inspecciona el conjunto, verifica el token y copia después los mismos ajustes a tu código.

Prueba las herramientas de claves en tu navegador

Decodifica primero el token y compara su kid con el conjunto antes de culpar a la firma.
Inspecciona todo JWKS que no hayas generado, y revisa hasPrivate antes de pegarlo en cualquier sitio.
Fija el algoritmo, el emisor y la audiencia en tu configuración.
Guarda el JWKS en caché y recárgalo cuando un token lleve un kid desconocido.
Considera la firma válida como la primera comprobación, no como una autorización.
Compartir este artículo
Compartir este artículo: