diff --git a/examples/jwks_client.rs b/examples/jwks_client.rs index c170f7e..19a9810 100644 --- a/examples/jwks_client.rs +++ b/examples/jwks_client.rs @@ -4,7 +4,6 @@ async fn main() -> jwtk::Result<()> { use jwtk::jwk::RemoteJwksVerifier; use serde::Deserialize; use serde_json::{Map, Value}; - use std::time::Duration; #[derive(Deserialize)] struct Token { @@ -16,12 +15,7 @@ async fn main() -> jwtk::Result<()> { .json() .await?; - let j = RemoteJwksVerifier::new( - "http://127.0.0.1:3000/jwks".into(), - None, - Duration::from_secs(300), - None, - ); + let j = RemoteJwksVerifier::new("http://127.0.0.1:3000/jwks".into()); let c = j.verify::>(&v.token).await?; println!("headers:\n{}", serde_json::to_string(c.header())?); diff --git a/src/jwk.rs b/src/jwk.rs index 22ad306..2520d5e 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -461,28 +461,19 @@ pub struct RemoteJwksVerifier { #[cfg(feature = "remote-jwks")] impl RemoteJwksVerifier { - pub fn new( - url: String, - client: Option, - cache_duration: std::time::Duration, - cooldown: Option, - ) -> Self { - Self { - url, - client: client.unwrap_or_default(), - cache_duration, - cooldown: cooldown.unwrap_or(std::time::Duration::from_secs(30)), - cache: tokio::sync::RwLock::new(None), - require_kid: true, - } + /// Construct a new RemoteJwksVerifier with default settings. + pub fn new(url: String) -> Self { + Self::builder(url).build() } - /// If called with `false`, subsequent `verify` and `verify_only` calls will - /// try all keys from the key set if a `kid` is not specified in the token. - pub fn set_require_kid(&mut self, required: bool) { - self.require_kid = required; - if let Some(ref mut v) = self.cache.get_mut() { - v.jwks.require_kid = required; + /// Construct a customized RemoteJwksVerifier. + pub fn builder(url: String) -> RemoteJwksVerifierBuilder { + RemoteJwksVerifierBuilder { + url, + client: None, + cache_duration: None, + cooldown: None, + require_kid: true, } } @@ -550,6 +541,13 @@ impl RemoteJwksVerifier { return err; } let mut cache = self.cache.write().await; + if cache + .as_ref() + .filter(|c| c.fresher_than(self.cooldown)) + .is_some() + { + return cache.as_ref().unwrap().jwks.verify(token); + } self.reload_jwks(&mut cache).await?; cache.as_ref().unwrap().jwks.verify(token) } @@ -574,6 +572,13 @@ impl RemoteJwksVerifier { return err; } let mut cache = self.cache.write().await; + if cache + .as_ref() + .filter(|c| c.fresher_than(self.cooldown)) + .is_some() + { + return cache.as_ref().unwrap().jwks.verify_only(token); + } self.reload_jwks(&mut cache).await?; cache.as_ref().unwrap().jwks.verify_only(token) } @@ -582,6 +587,61 @@ impl RemoteJwksVerifier { } } +#[cfg(feature = "remote-jwks")] +pub struct RemoteJwksVerifierBuilder { + url: String, + client: Option, + cache_duration: Option, + cooldown: Option, + require_kid: bool, +} + +#[cfg(feature = "remote-jwks")] +impl RemoteJwksVerifierBuilder { + /// Provide an HTTP client for fetching the JWK set. + pub fn with_client(mut self, client: reqwest::Client) -> Self { + self.client = Some(client); + self + } + + /// Set how long the fetched JWK set should be cached. Default is + /// 5 minutes. + pub fn with_cache_duration(mut self, duration: std::time::Duration) -> Self { + self.cache_duration = Some(duration); + self + } + + /// Set cooldown for reloading JWKs in response to unknown `kid`. + /// Default is 30 seconds. + pub fn with_cooldown(mut self, duration: std::time::Duration) -> Self { + self.cooldown = Some(duration); + self + } + + /// Calls to `verify` and `verify_only` calls will try all keys + /// from the key set if a `kid` is not specified in the token. + pub fn with_kid_optional(mut self) -> Self { + self.require_kid = false; + self + } + + /// Construct the RemoteJwksVerifier. + pub fn build(self) -> RemoteJwksVerifier { + RemoteJwksVerifier { + url: self.url, + client: self.client.unwrap_or_default(), + cache_duration: self + .cache_duration + .unwrap_or_else(|| std::time::Duration::from_secs(300)), + cooldown: self + .cooldown + .unwrap_or_else(|| std::time::Duration::from_secs(30)), + cache: tokio::sync::RwLock::new(None), + require_kid: self.require_kid, + } + } +} + #[cfg(test)] mod tests { use crate::{