Definicion
Imagina que eres un cajero de banco que necesita verificar que los cheques estan legitimamente firmados por los titulares de las cuentas. No tienes la firma de todos en archivo - en su lugar, tienes un directorio donde puedes buscar la firma oficial para cualquier numero de cuenta. Cuando llega un cheque, buscas la firma, la comparas con la del cheque, y la aceptas o rechazas. JWKS (JSON Web Key Set) es este directorio de firmas para el mundo digital - es un endpoint publico donde las aplicaciones pueden buscar las claves criptograficas necesarias para verificar que los JWTs (JSON Web Tokens) son autenticos.
Cuando inicias sesion usando “Iniciar sesion con Google” o cualquier proveedor OAuth/OpenID Connect, recibes JWTs que estan criptograficamente firmados. Pero como verifica tu aplicacion esa firma? Necesita la clave publica de Google. En lugar de codificar las claves (lo cual romperia cuando Google las rote), tu aplicacion obtiene el JWKS de Google desde un endpoint bien conocido como https://www.googleapis.com/oauth2/v3/certs. Este endpoint devuelve un objeto JSON conteniendo todas las claves publicas actuales de Google. Tu app encuentra la clave correcta (coincidiendo por kid - key ID en el header del JWT) y la usa para verificar la firma.
El “Set” en JWKS es crucial - los proveedores publican multiples claves para rotacion de claves. Google podria tener las claves “key-001” y “key-002” activas simultaneamente. Cuando rotan, anaden “key-003” y eventualmente eliminan “key-001”. Tu aplicacion no necesita saber sobre las rotaciones - simplemente obtiene el JWKS y encuentra la clave coincidente. Esta rotacion sin interrupciones es por que JWKS es el estandar para autenticacion distribuida. Cada proveedor de identidad importante (Google, Auth0, Okta, Azure AD) publica un endpoint JWKS, y cada libreria de JWT sabe como usarlo.
Ejemplo
Escenario Real 1: Verificando Tokens de Google Sign-In
Tu app implementa “Iniciar sesion con Google”. Cuando los usuarios se autentican, Google envia un ID token (un JWT) a tu app. El header del JWT contiene "kid": "12345abc". Tu app obtiene el JWKS de Google desde https://www.googleapis.com/oauth2/v3/certs, encuentra la clave con el kid coincidente, y usa esa clave publica para verificar la firma del JWT. Si la verificacion tiene exito, sabes que el token genuinamente vino de Google.
Escenario Real 2: Configuracion Multi-Tenant de Auth0
Tu app SaaS usa Auth0 para autenticacion. Cada cliente (tenant) tiene su propio dominio de Auth0. Cuando llegan tokens, tu app determina el tenant del claim issuer (iss), construye la URL del JWKS (https://{tenant}.auth0.com/.well-known/jwks.json), obtiene las claves y verifica. El patron JWKS te permite soportar multiples proveedores de identidad con la misma logica de verificacion.
Escenario Real 3: Validacion de Tokens en Microservicios En una arquitectura de microservicios, cada servicio necesita validar JWT access tokens. En lugar de compartir secretos (pesadilla de seguridad), el servicio de auth publica un endpoint JWKS. Cada microservicio obtiene y cachea estas claves. Cuando el servicio de auth rota las claves, los servicios automaticamente obtienen las nuevas claves del JWKS. Cero coordinacion necesaria, cero tiempo de inactividad durante la rotacion.
Escenario Real 4: Rotacion de Claves en Produccion
Tu proveedor de identidad necesita rotar claves (mejor practica de seguridad). Anaden una nueva clave al JWKS mientras mantienen la antigua. Los nuevos tokens usan la nueva clave (kid: "2024-key"), los tokens antiguos aun validan con la clave antigua (kid: "2023-key"). Despues de 24 horas, eliminan la clave antigua. Las aplicaciones con TTLs de cache de JWKS razonables manejan esto de forma transparente - sin cambios de codigo, sin interrupciones.
Analogia
El Directorio Publico de Notarios: Imagina un directorio de notarios publicos con sus patrones de sello oficiales. Cuando recibes un documento notariado, buscas el sello del notario en el directorio para verificar que es autentico. JWKS es este directorio - contiene los “sellos oficiales” (claves publicas) que los proveedores de identidad usan para firmar tokens.
El Registro de Sellos de Embajadas: Las embajadas usan sellos oficiales en documentos. Un registro central publica que patrones de sello pertenecen a que embajada. Cuando recibes un documento sellado, verificas contra el registro. JWKS es el registro de sellos para JWTs - te dice que claves publicas pertenecen a que emisores.
La Guia Telefonica de Firmas: En los viejos tiempos, los bancos mantenian tarjetas de firma en archivo. Cuando llegaba un cheque, comparaban la firma con la tarjeta. JWKS es una guia telefonica digital donde los proveedores de identidad publican sus patrones de firma (claves publicas). Cualquiera puede buscar el patron para verificar una firma.
La Autoridad Certificadora: HTTPS usa autoridades certificadoras para verificar sitios web. JWKS es como una mini-CA para JWTs - publica las claves publicas que verifican firmas de tokens. En lugar de confiar en una cadena de certificados, confias en el endpoint JWKS del emisor.
Ejemplo de Codigo
// Ejemplo de respuesta de endpoint JWKS
// GET https://oauth.provider.com/.well-known/jwks.json
{
"keys": [
{
"kty": "RSA", // Tipo de clave
"kid": "key-2024-001", // Key ID (coincide con header del JWT)
"use": "sig", // Uso de la clave: firma
"alg": "RS256", // Algoritmo
"n": "0vx7agoebGcQSuuPiLJXZpt...N4IOJnoEhw", // Modulo RSA
"e": "AQAB" // Exponente RSA
},
{
"kty": "RSA",
"kid": "key-2024-002", // Segunda clave para rotacion
"use": "sig",
"alg": "RS256",
"n": "xK94kVtxLhP...gqI7Y",
"e": "AQAB"
}
]
}
// Obtener y usar JWKS para verificar JWT
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
// Crear cliente con cache
const client = jwksClient({
jwksUri: 'https://oauth.provider.com/.well-known/jwks.json',
cache: true, // Cachear claves
cacheMaxAge: 600000, // Cache de 10 minutos
rateLimit: true, // Prevenir DoS en endpoint JWKS
jwksRequestsPerMinute: 10
});
// Funcion para obtener clave de firma por kid
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) {
console.error('Clave no encontrada:', header.kid);
return callback(err);
}
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
// Verificar token usando JWKS
async function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, {
algorithms: ['RS256'],
audience: 'tu-client-id',
issuer: 'https://oauth.provider.com'
}, (err, decoded) => {
if (err) {
return reject(new Error(`Verificacion de token fallida: ${err.message}`));
}
resolve(decoded);
});
});
}
// Middleware para Express
const verifyJWT = async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Token faltante' });
}
const token = authHeader.substring(7);
try {
req.user = await verifyToken(token);
next();
} catch (error) {
res.status(401).json({ error: 'Token invalido' });
}
};
// Discovery: Encontrar URL de JWKS desde Configuracion OpenID
// GET https://oauth.provider.com/.well-known/openid-configuration
{
"issuer": "https://oauth.provider.com",
"jwks_uri": "https://oauth.provider.com/.well-known/jwks.json",
"authorization_endpoint": "...",
"token_endpoint": "..."
}
Diagrama
sequenceDiagram
participant App as Tu App
participant IDP as Proveedor de Identidad
participant JWKS as Endpoint JWKS
Note over App: Recibe JWT con kid en header
App->>App: 1. Decodificar header del JWT
kid: "key-2024-001"
App->>JWKS: 2. GET /.well-known/jwks.json
JWKS->>App: 3. Devolver set de claves
Note over App: {
"keys": [
{ "kid": "key-2024-001", "n": "...", "e": "..." },
{ "kid": "key-2024-002", "n": "...", "e": "..." }
]
}
App->>App: 4. Encontrar clave que coincide con kid
App->>App: 5. Verificar firma JWT con clave publica
alt Firma Valida
App->>App: 6. Aceptar token, extraer claims
else Firma Invalida
App->>App: 6. Rechazar token
end
Note over JWKS: Las claves rotan periodicamente.
Multiples claves permiten rotacion sin interrupciones.