Skip to content

[Feature]: Support JWKS URI for JWT Key Rotation #100

@btiernay

Description

@btiernay

Problem statement

XMCP's current JWT validation logic requires a statically configured JWK via config.jwt.jwk. This approach works for simple cases but has several limitations:

  1. No Key Rotation Support: Static JWKs don’t allow for automatic key rollover from providers like Auth0, Okta, or Azure AD
  2. No kid Matching: If the JWT contains a kid in its header, there’s no mechanism to resolve that key from a remote key set
  3. Provider Lock-In: Many OAuth providers expose only a .well-known/jwks.json URL for key discovery, which cannot be used without JWKS support

Proposed solution

Extend config.jwt to support a jwksUri field, and use jose's createRemoteJWKSet() to dynamically fetch and cache keys for JWT verification.

Suggested Config Schema Addition

const jwtConfigSchema = z.object({
  issuer: z.string(),
  audience: z.string().optional(),
  jwk: z.record(z.any()).optional(),       // existing
  jwksUri: z.string().url().optional(),    // new
});

Suggested Validation Implementation

import { createRemoteJWKSet, jwtVerify, decodeJwt } from "jose";

if (this.config.jwt?.jwksUri) {
  const JWKS = createRemoteJWKSet(new URL(this.config.jwt.jwksUri));
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: this.config.jwt.issuer,
    audience: this.config.jwt.audience,
  });

  return {
    token,
    clientId: payload.azp ?? payload.client_id ?? "jwt",
    scopes: payload.scope?.split(" ") ?? [],
    expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
  };
}

Benefits

  1. Standards Compliant: Aligns with RFC 7517 and common OpenID Connect discovery patterns
  2. Key Rotation Ready: Automatically supports key rollover via JWKS
  3. Secure: Signature verification will correctly respect the JWT’s kid header
  4. Interoperable: Makes XMCP compatible with more real-world OAuth 2.0 and OIDC providers

Implementation Notes

  • Fallback to jwk if jwksUri is not configured
  • createRemoteJWKSet includes built-in caching and rate limiting
  • Consider exposing JWKS cache options later if needed (e.g., TTL)

Package

xmcp (core framework)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions