Cada vez que una API recibe un Authorization: Bearer … tiene que responder una pregunta en milisegundos: ¿este token es auténtico y sigue siendo válido? La respuesta no es «preguntarle a una base de datos», sino verificar una firma criptográfica. Y para verificar esa firma sin compartir ningún secreto entra en juego JWKS. En este artículo desarmamos el JWT pieza por pieza, seguimos el proceso de validación paso a paso y explicamos qué es exactamente un JWKS y por qué hace que todo escale.
Qué es un JWT por dentro
Un JSON Web Token es una cadena de texto con tres partes separadas por puntos: header.payload.signature. Cada parte es un JSON codificado en Base64Url (no cifrado: cualquiera puede leerlo). Lo importante es que la tercera parte, la firma, hace que el contenido sea a prueba de manipulación.
- Header: dice cómo está firmado el token. Dos campos clave:
alg(el algoritmo, p. ej.RS256) ykid(el identificador de la clave que lo firmó). - Payload: los claims, datos sobre el usuario y el token:
sub(quién),iss(quién lo emitió),aud(para quién es),exp(cuándo expira). - Signature: el resultado de firmar
header.payloadcon la clave del emisor. Es lo único que no se puede falsificar.
⚠️ El payload NO es secreto
Firmar y verificar: la magia de la criptografía asimétrica
Hay dos familias de algoritmos de firma. Con los simétricos (HS256) se usa la misma clave secreta para firmar y para verificar: quien verifica podría también falsificar tokens. Con los asimétricos (RS256, ES256) hay un par de claves: una privada que solo tiene el emisor y firma, y una pública que cualquiera puede usar para verificar pero no para firmar.
Esta asimetría es lo que hace práctico el modelo distribuido: puedes tener cientos de APIs validando tokens, todas con la clave pública, sin que ninguna pueda emitir tokens falsos. Por eso los proveedores de identidad serios (Cognito, Auth0, Keycloak, Google) firman con RS256 y no con HS256.
🚫 El ataque clásico: alg = none y la confusión de algoritmos
alg: none (un token «sin firma» que algunas librerías mal configuradas daban por válido); y (2) la confusión de algoritmos, donde un atacante cambia RS256 por HS256 y usa la clave pública (que es conocida) como si fuera el secreto HMAC. La defensa: fija el algoritmo esperado en el verificador y nunca dejes que el token decida cómo quiere ser validado.El proceso de validación, paso a paso
Cuando llega una petición con un JWT, la API no solo comprueba la firma: valida una serie de condiciones en orden. Si cualquiera falla, el token se rechaza con 401.
- 1-2. Leer el header. La API extrae
algykid. Elkiddice cuál de las claves públicas del emisor hay que usar. - 3-4. Obtener la clave pública. Si no la tiene cacheada, consulta el endpoint JWKS del emisor y busca la clave cuyo
kidcoincide. - 5. Verificar la firma. Recalcula la firma de
header.payloadcon esa clave pública y la compara con la del token. Si no cuadra, el token fue alterado o lo firmó otro. - 6. Validar los claims. Aunque la firma sea válida, hay que comprobar:
iss(¿lo emitió quien espero?),aud(¿es para mi API?),exp(¿no ha expirado?) ynbf(¿ya es válido?). - 7. Decidir. Firma correcta y claims correctos → sigue. Si no →
401 Unauthorized.
⚠️ Verificar la firma no es suficiente
Qué es JWKS y por qué existe
JWKS (JSON Web Key Set) es simplemente un documento JSON, publicado por el emisor en una URL conocida (normalmente /.well-known/jwks.json), que contiene el conjunto de claves públicas con las que firma sus tokens. Cada clave trae su propio kid, de modo que el verificador sabe cuál usar.
Un JWKS real se ve así:
GET https://auth.example.com/.well-known/jwks.json
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "a1b2c3",
"alg": "RS256",
"n": "0vx7agoebGcQSuuPiLJXZptN...", // módulo (clave pública)
"e": "AQAB" // exponente
},
{
"kty": "RSA",
"use": "sig",
"kid": "x9y8z7",
"alg": "RS256",
"n": "qDi7Tx4DhNvZ8Yx2...",
"e": "AQAB"
}
]
}JWKS resuelve dos problemas de golpe:
- Distribución de claves sin secretos. Las APIs no necesitan que nadie les «entregue» la clave: la descargan del endpoint público. Añadir una API nueva no requiere configurar nada en el emisor.
- Rotación de claves transparente. El emisor puede introducir una clave nueva (nuevo
kid) y empezar a firmar con ella, manteniendo la anterior en el set mientras existan tokens vigentes firmados con ella. Como el verificador elige porkid, la rotación no rompe nada.
El rol del kid en la rotación
El kid del header es el pegamento entre el token y el JWKS. Cuando el emisor rota claves, los tokens viejos siguen trayendo el kid antiguo y los nuevos elkid nuevo; ambas claves conviven en el JWKS durante la transición. Por eso una API bien hecha, si recibe un kid desconocido, refresca el JWKS (una vez) antes de rechazar: quizá es una clave recién rotada.
Cachear el JWKS: el detalle que no se puede olvidar
Descargar el JWKS en cada request sería un desastre de rendimiento y una dependencia dura del emisor. La práctica correcta es cachear las claves y refrescarlas solo cuando aparece un kid desconocido o cuando vence un TTL prudente.
# Pseudocódigo del verificador (patrón habitual con una librería JWT)
claves = cache.get_jwks() # cacheado en memoria
token = leer_bearer(request)
header = decodificar_header(token) # sin verificar aún
clave = claves.por_kid(header["kid"])
if clave is None: # kid nuevo -> quizá rotaron
claves = cache.refresh_jwks() # refresca UNA vez
clave = claves.por_kid(header["kid"])
payload = jwt.verificar(
token,
clave_publica=clave,
algoritmos=["RS256"], # fija el alg: nunca confíes en el header
issuer="https://auth.example.com", # valida iss
audience="mi-api", # valida aud
) # la librería también valida exp/nbf
# si algo falla -> excepción -> 401✅ Deja que una librería madura haga el trabajo
PyJWT con PyJWKClient, jose, o la del propio proveedor) que ya maneja Base64Url, selección por kid, fijado de algoritmo y validación de claims. Reinventar criptografía es la receta para introducir bugs de seguridad.Buenas prácticas al validar JWT
- Fija el algoritmo esperado (
RS256); rechazanoney HMAC si firmas con RSA. - Valida siempre
iss,audyexp, no solo la firma. - Cachea el JWKS y refréscalo ante un
kiddesconocido; nunca lo descargues por request. - Usa tokens de vida corta: el JWT es autovalidable, así que «revocarlo» antes de
expes difícil. - Si necesitas revocación inmediata, apóyate en tokens cortos + refresh, o una lista de revocación consultada aparte.
- Transporta el token solo por TLS y, en navegador, evita exponerlo (patrón BFF + cookie HttpOnly).
💡 JWT es stateless: su mayor virtud y su mayor límite
En resumen
Validar un JWT es verificar una firma criptográfica y luego comprobar sus claims. Con algoritmos asimétricos (RS256), el emisor firma con su clave privada y cualquier API verifica con la pública, sin compartir secretos. JWKS es el endpoint donde el emisor publica esas claves públicas, identificadas por kid, lo que permite distribuir claves y rotarlas sin tocar las APIs. Cachea el JWKS, fija el algoritmo, valida siempre los claims y usa tokens de vida corta: con eso tienes autenticación distribuida, segura y que escala.