From e7a0d269cf31edd4d9491f798abe98d8e7d7f708 Mon Sep 17 00:00:00 2001 From: Rafael Alvarez Date: Thu, 10 Sep 2026 15:32:38 -0400 Subject: [PATCH] fix(jwt): expire the JWKS host cache after poll_interval The cache was built with .timeout(poll_interval) and no time_to_live. timeout is the wait for a concurrent fetch, not the entry lifetime, and the SDK's default TTL is None, so the JWKS fetched at startup was served for the life of the process. After a provider key rotation every token signed by the new key was rejected with a 401 until the gateway restarted. Set time_to_live(poll_interval), which is what the README already documents the setting to mean, and drop the timeout override so the 5s SDK default applies. --- extensions/jwt/CHANGELOG.md | 4 ++++ extensions/jwt/src/lib.rs | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/extensions/jwt/CHANGELOG.md b/extensions/jwt/CHANGELOG.md index 1b5c94c..15bc467 100644 --- a/extensions/jwt/CHANGELOG.md +++ b/extensions/jwt/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Unreleased] + +- Fixed: the JWKS host cache never expired, so `poll_interval` only re-read the key set fetched at startup and a provider key rotation was not picked up until the gateway restarted. The cache entry now lives for `poll_interval`. + ## [1.3.0] - 2025-07-15 - Added support for static headers returned with 401 responses. diff --git a/extensions/jwt/src/lib.rs b/extensions/jwt/src/lib.rs index 2bd0958..831ba5b 100644 --- a/extensions/jwt/src/lib.rs +++ b/extensions/jwt/src/lib.rs @@ -31,7 +31,13 @@ impl AuthenticationExtension for Jwt { let config: Config = config.deserialize()?; Ok(Self { - jwks_cache: Cache::builder("jwks", 1).timeout(config.poll_interval).build(), + // `timeout` bounds the wait for a concurrent fetch; it is not the entry lifetime. Without a + // TTL the JWKS fetched at startup lives for the whole process, so a provider key rotation is + // invisible until a restart and every token signed by the new key is rejected with a 401. + // Expire the entry after `poll_interval`, which is what the README promises. + jwks_cache: Cache::builder("jwks", 1) + .time_to_live(Some(config.poll_interval)) + .build(), jwks: None, config, })