Firmar y verificar JWT

Un JWT son tres segmentos base64url — cabecera, carga útil, firma — y los dos primeros los puede leer cualquiera que tenga el token. Decodificar uno te dice lo que afirma; solo el tercer segmento te dice si debes creerlo. Este artículo trata de ese tercer segmento: cómo se hace una firma, quién puede hacer una, cómo la comprueban los verificadores y el punto exacto en el que «verificado» deja de significar «de confianza». Puedes ejecutar cada paso tú mismo en el firmante y verificador de JWT, que firma y verifica localmente en tu navegador.

Decodificar no es verificar

El malentendido más común sobre los JWT es también el más peligroso: creer que leer un token es lo mismo que comprobarlo. No lo es. La cabecera y la carga útil son base64url — una codificación, no un cifrado —, así que cualquiera que intercepte un token, lo encuentre en un registro o lo copie del almacenamiento local de un navegador puede decodificarlo en segundos, sin ninguna clave. Los claims que contiene (para quién es, cuándo caduca, qué roles afirma) son afirmaciones, y las afirmaciones son baratas.

La firma existe precisamente porque los claims son baratos. Es un vínculo criptográfico entre la cabecera, la carga útil y una clave que solo tiene el emisor: cambia un carácter de la carga útil y la firma deja de coincidir. Verificar es el acto de repetir esas matemáticas: recalcular la firma sobre la cabecera y la carga útil recibidas, con la clave del emisor, y comparar. Si coincide, el token es exactamente como lo firmó el emisor; si no, algo cambió, y el token no vale nada por muy plausibles que parezcan sus claims.

Simétrico frente a asimétrico: quién puede crear un token

Con HS256, el secreto que firma es el mismo secreto que verifica. Eso hace que HMAC sea rápido, sencillo y totalmente adecuado dentro de un solo servicio: tu API firma sus propios tokens y los comprueba ella misma, y el secreto nunca sale del edificio. En el momento en que una segunda parte necesita verificar, el modelo se rompe: entregarle el secreto es entregarle el poder de crear tokens, porque verificar y firmar son la misma operación. Un secreto HMAC filtrado no es un problema de verificación; es un problema de falsificación.

Los algoritmos asimétricos separan los dos poderes. RS256, ES256 y EdDSA firman con una clave privada y verifican con una clave pública, así que el emisor puede publicar sus claves públicas — como una JWKS — y todos los verificadores del mundo pueden comprobar tokens sin tener jamás el poder de falsificar uno. Esa separación es lo que hace que los JWT funcionen entre servicios: tu API confía en los tokens de un proveedor de identidad porque confía en las claves publicadas de ese proveedor, no porque comparta un secreto con él. El precio es la complejidad — formatos de clave, rotación, correspondencia de kid —, que es lo que el firmante y verificador de este sitio existe para hacer concreto.

El ataque de confusión de algoritmos

El ataque clásico contra JWT no necesita criptografía rota: necesita un verificador que confíe en que el token nombre su propio algoritmo. El atacante toma un token RS256 legítimo, cuya clave pública está publicada (como lo están las claves públicas), y vuelve a firmar la misma carga útil con HS256, usando la propia clave pública como secreto HMAC. Un verificador que lee alg de la cabecera y elige su rutina de verificación en consecuencia «verificará» ahora el token falsificado con HMAC, usando la clave pública como secreto, y la clave pública es pública.

La defensa es estructural, no un consejo: el verificador debe fijar los algoritmos que acepta, y la familia de la clave debe coincidir con la familia del algoritmo: una clave RSA nunca puede verificar un token HMAC, y un secreto nunca puede verificar un token RSA. Un verificador que reinterpreta una clave entre familias está roto por diseño, por muy correcta que sea su criptografía. Por eso el verificador de este sitio rechaza las claves de otra familia con una advertencia explícita en lugar de intentar la comprobación: la negativa es la función.

Cómo son en la práctica las JWKS y la rotación por kid

Un proveedor de identidad no entrega una única clave eterna; publica un conjunto — una JWKS, normalmente en /.well-known/jwks.json — y rota las claves añadiendo una nueva y, tras un periodo de gracia, retirando la antigua. La cabecera de cada token lleva un kid que nombra la clave que lo firmó, así que el verificador descarga el conjunto, elige la clave correspondiente y verifica. Los tokens firmados con la clave antigua siguen funcionando hasta que se retira, y eso es lo que hace que la rotación sea segura en lugar de un día de cambio brusco.

Los detalles operativos son donde se rompe. Los verificadores deben guardar la JWKS en caché (descargarla en cada petición es provocarse una caída uno mismo), gestionar el caso de un kid desconocido — normalmente una rotación que la caché aún no ha visto, así que hay que volver a descargar antes de fallar — y no aceptar nunca un conjunto de claves de una URL en la que no se confía: el conjunto solo es tan fiable como el canal por el que llegó, por eso los endpoints de JWKS viven detrás de HTTPS y por eso el creador de JWKS de aquí deriva cada kid como una huella RFC 7638 de la propia clave, para que el id no se pueda falsificar con independencia de la clave.

exp, nbf, iat, y el problema del reloj

Una firma demuestra que un token se firmó; no dice nada sobre si debe seguir honrándose. Ese es el trabajo de los claims de tiempo: exp (cuándo deja de ser válido), nbf (cuándo empieza a serlo), iat (cuándo se emitió). Un verificador que comprueba la firma y se salta el reloj acepta un token para siempre, incluido uno robado hace un año. Las vidas cortas son la respuesta práctica: tokens de acceso medidos en minutos, tokens de actualización con la confianza de larga duración, y la revocación resuelta simplemente no volviendo a emitir.

Los relojes no coinciden, así que los verificadores permiten un poco de margen — normalmente uno o dos minutos de tolerancia en exp y nbf —, pero el margen es una tolerancia para la deriva, no una licencia para ignorar la caducidad. Un dispositivo cuyo reloj va minutos desfasado producirá tokens que su propio servidor rechaza, y un token con un iat en el futuro es una señal de falsificación, no un error de redondeo. La misma disciplina de reloj se aplica a las herramientas: el verificador comprueba la caducidad con el reloj que puedes ver, y dice claramente cuándo un token ha caducado en lugar de limitarse a «no válido».

Qué demuestra — y qué no demuestra — un «verificado»

Un token verificado demuestra tres cosas y nada más: la cabecera y la carga útil son exactamente como se firmaron, el firmante tenía la clave y el token está dentro de su ventana de validez. No demuestra que el emisor sea quien crees que es: eso depende de dónde venga la clave, y una clave descargada de una URL controlada por un atacante verifica perfectamente los tokens del atacante. No demuestra que los claims sean ciertos: una firma autentica la afirmación, no los hechos que hay detrás. Y no demuestra que el token no haya sido robado: un token válido presentado por cualquiera se acepta, por eso los tokens son de vida corta y con alcance limitado.

Así que la decisión de confianza vive fuera del token. Tu API confía en un token porque descargó la clave de la URL conocida de su proveedor de identidad por HTTPS, porque el iss coincide con el proveedor que configuró y porque el aud coincide con el servicio que es. La verificación es la última comprobación, no la primera: procedencia de la clave, emisor, audiencia, luego firma, luego tiempo. Una herramienta que informa «VÁLIDO» sin ese contexto dice la verdad sobre las matemáticas y nada sobre la confianza, por eso el verificador de este sitio lo dice en la página, junto al veredicto.

Preguntas frecuentes

¿Puedo verificar un token sin la clave?

No: la verificación necesita la clave pública del emisor (o el secreto compartido, para HMAC). Decodificar no necesita clave, que es justo la confusión que hay que evitar: un token decodificado es legible, no verificado. Si el emisor publica una JWKS, descárgala de la URL conocida y haz coincidir el kid del token; el verificador de este sitio lo hace por ti.

¿Por qué mi token RS256 falla con mi secreto?

Porque un secreto y una clave RSA son familias distintas, y un verificador sólido se niega a mezclarlas. Los tokens RS256 se verifican con la clave pública RSA del emisor — PEM, JWK o JWKS —, nunca con un secreto de texto. Usar un secreto sería el ataque de confusión de algoritmos: la clave pública es pública, así que cualquiera podría falsificar tokens si la verificación HMAC la aceptara como secreto.

¿Cuánto debería vivir un JWT?

Minutos, no días, para los tokens de acceso: de 5 a 15 minutos es el rango habitual, con un token de actualización que lleva la sesión más larga. Una vida corta limita el daño de un token robado a la ventana antes de que caduque, y hace que la revocación sea cuestión de no volver a emitir en lugar de mantener una lista de bloqueo. Los tokens que viven semanas son cookies de sesión con peores herramientas.

Fuentes