use std::collections::{HashMap, HashSet};
use jwt_simple::{
algorithms::RSAPublicKeyLike,
prelude::{
Token,
Serialize,
VerificationOptions,
RS256PublicKey,
JWTClaims,
},
};
use chrono::{Duration, DateTime, Utc};
use serde::Deserialize;
use serde_with::{
serde_as,
base64::{Base64, UrlSafe},
formats::Unpadded,
};
mod error;
pub use error::JwkClientErr;
#[derive(Debug, Clone)]
pub struct JwkClient {
jwks_uri: String,
issuer: String,
audience: String,
public_keys: HashMap<String, PublicKey>, auto_refresh_interval: Duration,
retry_rate_limit: Duration,
last_refresh: Option<DateTime<Utc>>,
last_retry: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone)]
struct PublicKey {
key: RS256PublicKey,
not_before: Option<DateTime<Utc>>,
}
impl PublicKey {
fn is_valid(&self) -> bool {
self.not_before.is_none_or(|nbf| nbf <= Utc::now())
}
fn valid_key(&self) -> Option<&RS256PublicKey> {
self.is_valid().then_some(&self.key)
}
}
impl JwkClient {
pub fn new(
jwks_uri: impl Into<String>,
issuer: impl Into<String>,
audience: impl Into<String>,
) -> Self {
Self {
jwks_uri: jwks_uri.into(),
issuer: issuer.into(),
audience: audience.into(),
public_keys: HashMap::new(),
auto_refresh_interval: Duration::hours(1),
retry_rate_limit: Duration::minutes(5),
last_refresh: None,
last_retry: None,
}
}
pub fn set_auto_refresh_interval(&mut self, duration: Duration) {
self.auto_refresh_interval = duration;
}
pub fn set_retry_rate_limit(&mut self, duration: Duration) {
self.retry_rate_limit = duration;
}
fn keys_are_stale(&self) -> bool {
self.last_refresh
.map(|t| Utc::now() - t > self.auto_refresh_interval)
.unwrap_or(true)
}
fn can_retry_on_failure(&self) -> bool {
self.last_retry
.map(|t| Utc::now() - t > self.retry_rate_limit)
.unwrap_or(true)
}
async fn refresh_public_keys(&mut self) -> Result<(), JwkClientErr> {
let public_keys: Result<_, _> = reqwest::get(&self.jwks_uri)
.await?
.json::<JwkRawArray>()
.await?
.keys
.into_iter()
.map(|jwk| {
let key = RS256PublicKey::from_components(&jwk.modulus, &jwk.exponent)?;
Ok::<(std::string::String, PublicKey), JwkClientErr>((jwk.key_id, PublicKey {
key,
not_before: jwk.not_before,
}))
})
.collect();
self.public_keys = public_keys?;
self.last_refresh = Some(Utc::now());
Ok(())
}
fn get_valid_key(&self, key_id: &str) -> Option<&RS256PublicKey> {
self.public_keys
.get(key_id)?
.valid_key()
}
pub async fn validate_token<T>(&mut self, token: &str) -> Result<JWTClaims<T>, JwkClientErr>
where
for<'de> T: Serialize + Deserialize<'de>,
{
if self.keys_are_stale() {
self.refresh_public_keys().await?;
}
match self.validate_token_impl(token).await {
Err(_) if self.can_retry_on_failure() => {
self.refresh_public_keys().await?;
self.last_retry = Some(Utc::now());
self.validate_token_impl(token).await
},
result => result,
}
}
async fn validate_token_impl<T>(
&mut self,
token: &str,
) -> Result<JWTClaims<T>, JwkClientErr>
where
for<'de> T: Serialize + Deserialize<'de>,
{
let verification_options = VerificationOptions {
allowed_issuers: Some(HashSet::from([self.issuer.clone()])),
allowed_audiences: Some(HashSet::from([self.audience.clone()])),
..Default::default()
};
let metadata = Token::decode_metadata(token)?;
let key_id = metadata
.key_id()
.ok_or(JwkClientErr::Other("token is missing public key id `kid`".to_string()))?;
let key = self.get_valid_key(key_id)
.ok_or(JwkClientErr::Other("token's public key id `kid` not found".to_string()))?;
key.verify_token::<T>(token, Some(verification_options))
.map_err(JwkClientErr::from)
}
}
#[derive(Debug, Deserialize)]
struct JwkRawArray {
keys: Vec<JwkRaw>,
}
#[serde_as]
#[derive(Debug, Deserialize, Clone)]
struct JwkRaw {
#[serde(rename = "kid")]
key_id: String,
#[serde(rename = "nbf", with = "chrono::serde::ts_seconds_option")]
not_before: Option<DateTime<Utc>>,
#[serde_as(as = "Base64<UrlSafe, Unpadded>")]
#[serde(rename = "e")]
exponent: Vec<u8>,
#[serde_as(as = "Base64<UrlSafe, Unpadded>")]
#[serde(rename = "n")]
modulus: Vec<u8>,
}