Skip to content

Add stale_if_error option to PyJWKClient - #1239

Open
Graph3x wants to merge 9 commits into
jpadilla:masterfrom
Graph3x:jwks-stale-if-error
Open

Graph3x wants to merge 9 commits into
jpadilla:masterfrom
Graph3x:jwks-stale-if-error

Conversation

@Graph3x

@Graph3x Graph3x commented Oct 21, 2026 •

Copy link
Copy Markdown

When the cached JWK Set expires while the JWKS endpoint is unreachable, PyJWKClient raises PyJWKClientConnectionError and every token verification fails until the endpoint recovers, even though the keys fetched a few minutes earlier are almost certainly still valid.

This adds an opt-in stale_if_error parameter, named after RFC 5861. When refreshing an expired JWK Set fails, the client keeps serving the expired set for up to that many seconds past expiry instead of raising.

Behaviour

  • Serves stale on transport failures (OSError, http.client.HTTPException) and HTTP 500/502/503/504. Other HTTP errors and invalid payloads still raise.
  • The window counts from expiry. float("inf") serves stale for as long as fetching fails. NaN and negative values are rejected, as is a non-zero value with cache_jwk_set=False.
  • get_jwk_set(refresh=True) never falls back, so an unknown kid during an outage still reports the connection error.
  • The stale set is not written back to the cache, so repeated failures don't extend its life.
  • After a fallback, the client serves the stale set without fetching for cooldown_duration seconds. This reuses cooldown_duration rather than adding a parameter. cooldown_duration=0 disables it, and get_jwk_set(refresh=True) always fetches.
  • The fallback is silent.
  • JWKSetCache.get() and is_expired() take a max_stale argument to support this.

Security: during an outage, a key removed from the endpoint stays usable for up to lifespan + stale_if_error. Removal already takes up to lifespan to apply today (and with cache_keys=True it never does). The default of 0 keeps current behaviour, so the reliability vs. revocation trade-off is the user's choice.

Notes

  • The backoff is in the last two commits. If you'd rather keep this PR to stale_if_error alone, I can drop them and open the backoff separately.
  • An unknown kid still triggers a fetch during backoff, since it may indicate key rotation.
  • This doesn't depend on Raise the documented errors from PyJWKClient.fetch_data() #1238, but checking for OSError matches the wrapping it adds, so the two PRs work in either merge order.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant