Skip to content
Ashish.
All posts
Diagram illustrating the jwks_uri key discovery and caching flow.
6 min readDevelopmentbackend developers, identity engineersFeatured#jwks#jwks_uri#key-rotation#kid#caching#rfc-8414#oauth2#openid-connect

jwks_uri: Key Discovery, Rotation, and Caching

An examination of RFC 8414's JWKS URI for key discovery, key rotation strategies, and caching mechanisms for backend developers.

By Ashish SrivastavaPart 3 of Authorization Server Metadata (RFC 8414)

In OAuth 2.0 and OpenID Connect (OIDC) architectures, the JSON Web Token (JWT) is a stateless bearer token. To validate its signature, the Resource Server (RS) must possess the public key used by the Authorization Server (AS) to sign it. This process is critical for token validation in both oauth2 metadata and openid connect standards. Historically, this required manual distribution of keys. RFC 8414 standardized this process by introducing Authorization Server Metadata, a JSON document that describes the AS capabilities, including the jwks_uri.

This article examines the mechanism of jwks_uri discovery, the lifecycle of key rotation, and the critical caching strategies required to maintain both security and performance.

The Mechanism of Discovery

The jwks_uri is a URL pointing to a resource that contains a JSON Web Key Set (JWKS). A JWKS is a simple JSON object containing an array of public keys. Each key in the set is identified by a kid (Key ID).

When a client receives a JWT, the token header includes a kid field (optional but recommended). This kid acts as a pointer. The RS fetches the JWKS from jwks_uri, iterates through the keys, and selects the one whose kid matches the token’s header.

Consider this minimal JWKS response from an AS:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "rsa-key-1",
      "use": "sig",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmb...",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "kid": "rsa-key-2",
      "use": "sig",
      "n": "SuaGPYsRz...[truncated]...",
      "e": "AQAB"
    }
  ]
}

If a JWT arrives with {"alg": "RS256", "kid": "rsa-key-1"}, the RS uses rsa-key-1 to verify the signature. If the kid is missing or does not match any key in the JWKS, the token must be rejected.

Mechanism Insight: The jwks_uri decouples key distribution from the token issuance flow. The AS can rotate keys without notifying every client individually; clients simply re-fetch the metadata.

Key Rotation Strategies

Keys are not permanent. They expire due to policy, compromise, or algorithm deprecation. How does the AS manage this transition?

The Overlap Strategy

A secure AS does not instantly revoke an old key when a new one is created. Instead, it employs an overlap period. During this window, the AS signs new tokens with the new key but continues to publish the old key in the JWKS.

  1. T=0: New key key-B is generated. key-A is still active.
  2. T=0 to T=+24h: AS publishes both key-A and key-B in the JWKS.
  3. T=+1h: AS begins signing new tokens with key-B.
  4. T=+24h: AS removes key-A from the JWKS.

This ensures that tokens signed with key-A during the overlap period remain valid until their expiration time. If the RS were to purge key-A immediately upon seeing it removed from the JWKS, it would invalidate all still-active tokens signed by the old key, causing widespread authentication failures.

Operational Constraint: The RS must cache keys for at least the maximum lifetime of the tokens issued by that key. If tokens expire in 1 hour, the RS must retain the key in memory for at least 1 hour after fetching it, even if the JWKS no longer lists it.

Caching Mechanics and Security

Fetching the JWKS on every request is a performance anti-pattern. It introduces latency and increases load on the AS. However, caching introduces a security risk: stale keys. If an RS caches a JWKS that no longer contains the active key, it cannot verify new tokens.

RFC 8414 relies on standard HTTP caching mechanisms. The AS should return appropriate headers:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300
ETag: "abc123"

Caching Workflow

  1. First Request: RS requests jwks_uri. AS responds with JWKS and Cache-Control: max-age=300. RS stores the keys in memory with an expiry timestamp of 300 seconds.
  2. Subsequent Requests (within 300s): RS serves the cached keys. No network call is made.
  3. Cache Expiry: After 300s, the next request triggers a re-validation.
    • Conditional GET: RS sends If-None-Match: "abc123".
    • AS Response: If unchanged, AS returns 304 Not Modified. RS keeps the old cache.
    • AS Response: If changed, AS returns 200 OK with the new JWKS and a new ETag. RS updates its cache.

Critical Security Rule: Never cache JWKS indefinitely. Even if Cache-Control is missing, implement a hard TTL (Time-To-Live) of 5–10 minutes. This balances performance with the ability to detect key rotation in line with openid connect standards.

Handling Cache Misses and Errors

If the JWKS endpoint is unreachable, the RS should not fall back to a static key or reject the request outright without a grace period. A common pattern is:

  1. Fetch JWKS. Cache it.
  2. If JWKS fetch fails, use the last known good JWKS for a short window (e.g., 1 minute).
  3. If the last known good JWKS is also stale or missing the required kid, reject the token.

This prevents a cascading failure where a temporary network outage locks out all users.

Implementation Pitfalls

1. Ignoring the kid

Some libraries default to the first key in the JWKS if kid is missing. This is insecure. If the AS rotates keys, the first key might change meaning. Always validate that the kid in the token header matches the kid of the key used for verification. If kid is absent, consider rejecting the token unless your security policy explicitly allows it (e.g., internal services with single-key trust).

2. Issuer Mismatch

The jwks_uri must belong to the same issuer as the token. An attacker could host a malicious JWKS at attacker.com/jwks.json and trick a client into fetching it. Always validate that the iss claim in the JWT matches the hostname of the jwks_uri.

3. Timeout Handling

JWKS fetches should have strict timeouts (e.g., 2–5 seconds). A slow JWKS endpoint can block the entire request processing pipeline. Use asynchronous fetching and background refresh threads to keep the cache warm without blocking user requests.

Conclusion

jwks_uri is the cornerstone of secure, scalable JWT validation. It shifts the burden of key distribution from manual configuration to a dynamic, HTTP-based protocol. By understanding key rotation overlaps, implementing effective HTTP caching, and strictly validating kid and issuer fields, backend developers can build systems that are both secure and performant.

Remember: The cached JWKS is a snapshot in time. Your cache is the bridge between that snapshot and the present. Manage that bridge carefully.

Related posts