1use std::collections::HashMap;
9use std::fmt;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use async_trait::async_trait;
14use jiff::{SignedDuration, Timestamp};
15use jsonwebtoken::{Algorithm, DecodingKey, Validation};
16use tokio::sync::{Mutex, RwLock};
17use tracing::*;
18
19use crate::error::ErrorKind;
20use crate::IdTokenAudience;
21
22#[cfg(feature = "axum")]
23pub use crate::axum_layer::{VerifyIdToken, VerifyIdTokenLayer};
24pub use jsonwebtoken;
27pub use jsonwebtoken::jwk::JwkSet;
28
29const GOOGLE_KEYS_URL: &str = "https://www.googleapis.com/oauth2/v3/certs";
32
33const GOOGLE_ISSUERS: [&str; 2] = ["https://accounts.google.com", "accounts.google.com"];
35
36const LEEWAY: SignedDuration = SignedDuration::from_secs(30);
38
39const KEYS_DEFAULT_MAX_AGE: Duration = Duration::from_secs(5 * 60);
41
42const KEYS_MAX_AGE_LIMIT: Duration = Duration::from_secs(24 * 60 * 60);
44
45const REFETCH_INTERVAL: Duration = Duration::from_secs(30);
48
49const KEYS_FETCH_TIMEOUT: Duration = Duration::from_secs(10);
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct PrincipalEmail(String);
55
56impl PrincipalEmail {
57 pub fn new(email: impl Into<String>) -> Self {
58 Self(email.into())
59 }
60}
61
62impl fmt::Display for PrincipalEmail {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str(&self.0)
65 }
66}
67
68#[derive(Debug, Clone)]
71pub struct VerifiedIdToken {
72 audience: IdTokenAudience,
73 subject: String,
74 email: Option<PrincipalEmail>,
75 email_verified: Option<bool>,
76 hosted_domain: Option<String>,
77 expires_at: Timestamp,
78}
79
80impl VerifiedIdToken {
81 pub fn audience(&self) -> &IdTokenAudience {
83 &self.audience
84 }
85
86 pub fn subject(&self) -> &str {
88 &self.subject
89 }
90
91 pub fn email(&self) -> Option<&PrincipalEmail> {
94 self.email.as_ref()
95 }
96
97 pub fn email_verified(&self) -> Option<bool> {
99 self.email_verified
100 }
101
102 pub fn verified_email(&self) -> Option<&PrincipalEmail> {
104 match self.email_verified {
105 Some(true) => self.email.as_ref(),
106 Some(false) | None => None,
107 }
108 }
109
110 pub fn hosted_domain(&self) -> Option<&str> {
112 self.hosted_domain.as_deref()
113 }
114
115 pub fn expires_at(&self) -> Timestamp {
117 self.expires_at
118 }
119}
120
121#[derive(Debug)]
123pub enum IdTokenVerifyError {
124 InvalidToken(InvalidIdToken),
127 KeysUnavailable(Arc<crate::error::Error>),
132}
133
134impl fmt::Display for IdTokenVerifyError {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 Self::InvalidToken(reason) => write!(f, "invalid ID token: {reason}"),
138 Self::KeysUnavailable(error) => {
139 write!(f, "ID token signing keys are unavailable: {error}")
140 }
141 }
142 }
143}
144
145impl std::error::Error for IdTokenVerifyError {
146 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147 match self {
148 Self::InvalidToken(reason) => Some(reason),
149 Self::KeysUnavailable(error) => Some(error.as_ref()),
150 }
151 }
152}
153
154impl From<InvalidIdToken> for IdTokenVerifyError {
155 fn from(reason: InvalidIdToken) -> Self {
156 Self::InvalidToken(reason)
157 }
158}
159
160#[derive(Debug)]
162#[non_exhaustive]
163pub enum InvalidIdToken {
164 Malformed(jsonwebtoken::errors::Error),
166 UnknownKey(Option<String>),
168 BadSignature,
170 Expired,
172 NotYetValid,
174 WrongAudience,
176 WrongIssuer,
178}
179
180impl fmt::Display for InvalidIdToken {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 match self {
183 Self::Malformed(error) => write!(f, "malformed token: {error}"),
184 Self::UnknownKey(Some(kid)) => write!(f, "unknown signing key {kid}"),
185 Self::UnknownKey(None) => write!(f, "no signing key ID"),
186 Self::BadSignature => write!(f, "bad signature"),
187 Self::Expired => write!(f, "expired"),
188 Self::NotYetValid => write!(f, "not yet valid"),
189 Self::WrongAudience => write!(f, "wrong audience"),
190 Self::WrongIssuer => write!(f, "wrong issuer"),
191 }
192 }
193}
194
195impl std::error::Error for InvalidIdToken {
196 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
197 match self {
198 Self::Malformed(error) => Some(error),
199 _ => None,
200 }
201 }
202}
203
204impl From<jsonwebtoken::errors::Error> for InvalidIdToken {
205 fn from(error: jsonwebtoken::errors::Error) -> Self {
206 use jsonwebtoken::errors::ErrorKind as JwtErrorKind;
207 match error.kind() {
208 JwtErrorKind::InvalidSignature => Self::BadSignature,
209 JwtErrorKind::ExpiredSignature => Self::Expired,
210 JwtErrorKind::ImmatureSignature => Self::NotYetValid,
211 JwtErrorKind::InvalidAudience => Self::WrongAudience,
212 JwtErrorKind::InvalidIssuer => Self::WrongIssuer,
213 _ => Self::Malformed(error),
214 }
215 }
216}
217
218pub struct IdTokenKeys {
220 keys: JwkSet,
221 max_age: Duration,
222}
223
224impl IdTokenKeys {
225 pub fn new(keys: JwkSet, max_age: Duration) -> Self {
228 Self { keys, max_age }
229 }
230}
231
232#[async_trait]
235pub trait IdTokenKeysSource: Send + Sync {
236 async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys>;
238}
239
240struct GoogleKeys {
242 client: reqwest::Client,
243 url: String,
244}
245
246#[async_trait]
247impl IdTokenKeysSource for GoogleKeys {
248 async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys> {
249 let response = self.client.get(&self.url).send().await?;
250 let status = response.status();
251 if !status.is_success() {
252 return Err(ErrorKind::HttpStatus(status).into());
253 }
254 let max_age = cache_max_age(response.headers()).unwrap_or(KEYS_DEFAULT_MAX_AGE);
255 let keys = response.json::<JwkSet>().await?;
256 Ok(IdTokenKeys::new(keys, max_age))
257 }
258}
259
260fn cache_max_age(headers: &reqwest::header::HeaderMap) -> Option<Duration> {
262 let cache_control = headers.get(reqwest::header::CACHE_CONTROL)?.to_str().ok()?;
263 cache_control
264 .split(',')
265 .find_map(|directive| {
266 let (name, seconds) = directive.split_once('=')?;
267 name.trim()
268 .eq_ignore_ascii_case("max-age")
269 .then_some(seconds)
270 })
271 .and_then(|seconds| seconds.trim().parse().ok())
272 .map(Duration::from_secs)
273}
274
275pub struct IdTokenVerifier {
284 validation: Validation,
285 keys_source: Box<dyn IdTokenKeysSource>,
286 cache: RwLock<KeyCache>,
287 fetching: Mutex<()>,
289 refetch_interval: Duration,
290}
291
292impl IdTokenVerifier {
293 pub fn new(audience: IdTokenAudience) -> crate::error::Result<Self> {
297 let client = reqwest::Client::builder()
298 .user_agent(crate::GCLOUD_SDK_USER_AGENT)
299 .timeout(KEYS_FETCH_TIMEOUT)
300 .https_only(true)
301 .build()?;
302 Ok(Self::with_keys_source(
303 audience,
304 GoogleKeys {
305 client,
306 url: GOOGLE_KEYS_URL.to_string(),
307 },
308 ))
309 }
310
311 pub fn with_keys_source(
314 audience: IdTokenAudience,
315 keys_source: impl IdTokenKeysSource + 'static,
316 ) -> Self {
317 crate::jwt_crypto::ensure_provider();
318 let mut validation = Validation::new(Algorithm::RS256);
319 validation.set_audience(&[audience.as_str()]);
320 validation.set_issuer(&GOOGLE_ISSUERS);
321 validation.set_required_spec_claims(&["exp", "iss", "aud", "sub"]);
322 validation.leeway = LEEWAY.as_secs().unsigned_abs();
323 validation.validate_nbf = true;
324 Self {
325 validation,
326 keys_source: Box::new(keys_source),
327 cache: RwLock::new(KeyCache::default()),
328 fetching: Mutex::new(()),
329 refetch_interval: REFETCH_INTERVAL,
330 }
331 }
332
333 pub async fn verify(&self, token: &str) -> Result<VerifiedIdToken, IdTokenVerifyError> {
336 let header = jsonwebtoken::decode_header(token).map_err(InvalidIdToken::Malformed)?;
337 if header.alg != Algorithm::RS256 {
338 return Err(InvalidIdToken::Malformed(
339 jsonwebtoken::errors::ErrorKind::InvalidAlgorithm.into(),
340 )
341 .into());
342 }
343 let kid = header.kid.ok_or(InvalidIdToken::UnknownKey(None))?;
344 let key = self.key(kid).await?;
345 let claims = jsonwebtoken::decode::<GoogleIdTokenClaims>(token, &key, &self.validation)
346 .map_err(InvalidIdToken::from)?
347 .claims;
348 if claims.iat > Timestamp::now() + LEEWAY {
350 return Err(InvalidIdToken::NotYetValid.into());
351 }
352 Ok(claims.into())
353 }
354
355 async fn key(&self, kid: String) -> Result<Arc<DecodingKey>, IdTokenVerifyError> {
356 if let Some(found) = self.cache.read().await.lookup(&kid, self.refetch_interval) {
357 return found;
358 }
359 let _fetching = self.fetching.lock().await;
360 if let Some(found) = self.cache.read().await.lookup(&kid, self.refetch_interval) {
362 return found;
363 }
364 let fetched = self.keys_source.fetch_keys().await;
365 let mut cache = self.cache.write().await;
366 cache.last_fetch = Some(Instant::now());
369 match fetched {
370 Ok(keys) => {
371 cache.store(keys);
372 match cache.keys.get(&kid) {
373 Some(key) => Ok(Arc::clone(key)),
374 None => Err(InvalidIdToken::UnknownKey(Some(kid)).into()),
375 }
376 }
377 Err(error) => {
378 warn!(%error, "Fetching ID token signing keys failed");
379 let error = Arc::new(error);
380 cache.last_failure = Some(Arc::clone(&error));
381 Err(IdTokenVerifyError::KeysUnavailable(error))
382 }
383 }
384 }
385}
386
387#[derive(serde::Deserialize)]
388struct GoogleIdTokenClaims {
389 aud: String,
390 sub: String,
391 #[serde(with = "jiff::fmt::serde::timestamp::second::required")]
392 exp: Timestamp,
393 #[serde(with = "jiff::fmt::serde::timestamp::second::required")]
394 iat: Timestamp,
395 email: Option<String>,
396 email_verified: Option<bool>,
397 hd: Option<String>,
398}
399
400impl From<GoogleIdTokenClaims> for VerifiedIdToken {
401 fn from(claims: GoogleIdTokenClaims) -> Self {
402 Self {
403 audience: IdTokenAudience::new(claims.aud),
404 subject: claims.sub,
405 email: claims.email.map(PrincipalEmail::new),
406 email_verified: claims.email_verified,
407 hosted_domain: claims.hd,
408 expires_at: claims.exp,
409 }
410 }
411}
412
413#[derive(Default)]
415struct KeyCache {
416 keys: HashMap<String, Arc<DecodingKey>>,
417 fresh_until: Option<Instant>,
419 last_fetch: Option<Instant>,
421 last_failure: Option<Arc<crate::error::Error>>,
423}
424
425impl KeyCache {
426 fn lookup(
428 &self,
429 kid: &str,
430 refetch_interval: Duration,
431 ) -> Option<Result<Arc<DecodingKey>, IdTokenVerifyError>> {
432 let now = Instant::now();
433 let fresh = self.fresh_until.is_some_and(|until| now < until);
434 if fresh {
435 if let Some(key) = self.keys.get(kid) {
436 return Some(Ok(Arc::clone(key)));
437 }
438 }
439 let fetched_recently = self
440 .last_fetch
441 .is_some_and(|at| now.duration_since(at) < refetch_interval);
442 if !fetched_recently {
443 return None;
444 }
445 match &self.last_failure {
446 Some(error) => Some(Err(IdTokenVerifyError::KeysUnavailable(Arc::clone(error)))),
447 None if fresh => Some(Err(InvalidIdToken::UnknownKey(Some(kid.to_string())).into())),
448 None => None,
450 }
451 }
452
453 fn store(&mut self, fetched: IdTokenKeys) {
454 self.keys = fetched
455 .keys
456 .keys
457 .iter()
458 .filter_map(|jwk| {
459 let kid = jwk.common.key_id.clone()?;
460 match DecodingKey::from_jwk(jwk) {
461 Ok(key) => Some((kid, Arc::new(key))),
462 Err(error) => {
463 warn!(%kid, %error, "Skipping an unusable ID token signing key");
464 None
465 }
466 }
467 })
468 .collect();
469 self.fresh_until = Some(Instant::now() + fetched.max_age.min(KEYS_MAX_AGE_LIMIT));
470 self.last_failure = None;
471 }
472}
473
474#[cfg(all(test, any(feature = "jwt-aws-lc-rs", feature = "jwt-rust-crypto")))]
476mod tests {
477 use super::*;
478 use crate::test_support::{signed_jwt, test_jwk_set, StubResponse, StubServer};
479 use std::collections::VecDeque;
480 use std::sync::atomic::{AtomicUsize, Ordering};
481
482 const AUDIENCE: &str = "https://orders-abc123-ew.a.run.app";
483 const CALLER: &str = "caller@my-project.iam.gserviceaccount.com";
484 const CURRENT_KEY: &str = "current-key";
485 const ROTATED_KEY: &str = "rotated-key";
486
487 fn claims() -> serde_json::Value {
488 let now = Timestamp::now();
489 serde_json::json!({
490 "iss": "https://accounts.google.com",
491 "aud": AUDIENCE,
492 "sub": "112233445566778899",
493 "email": CALLER,
494 "email_verified": true,
495 "iat": now.as_second(),
496 "exp": (now + SignedDuration::from_hours(1)).as_second(),
497 })
498 }
499
500 struct ScriptedKeys {
502 results: std::sync::Mutex<VecDeque<crate::error::Result<IdTokenKeys>>>,
503 fetches: Arc<AtomicUsize>,
504 fetch_duration: Duration,
505 }
506
507 impl ScriptedKeys {
508 fn new(results: Vec<crate::error::Result<IdTokenKeys>>) -> (Self, Arc<AtomicUsize>) {
509 let fetches = Arc::new(AtomicUsize::new(0));
510 let source = Self {
511 results: std::sync::Mutex::new(results.into()),
512 fetches: Arc::clone(&fetches),
513 fetch_duration: Duration::ZERO,
514 };
515 (source, fetches)
516 }
517 }
518
519 #[async_trait]
520 impl IdTokenKeysSource for ScriptedKeys {
521 async fn fetch_keys(&self) -> crate::error::Result<IdTokenKeys> {
522 self.fetches.fetch_add(1, Ordering::SeqCst);
523 tokio::time::sleep(self.fetch_duration).await;
524 self.results
525 .lock()
526 .unwrap()
527 .pop_front()
528 .expect("a scripted result for every fetch")
529 }
530 }
531
532 fn keys(key_ids: &[&str]) -> crate::error::Result<IdTokenKeys> {
533 Ok(IdTokenKeys::new(
534 test_jwk_set(key_ids),
535 Duration::from_secs(3600),
536 ))
537 }
538
539 fn unavailable() -> crate::error::Result<IdTokenKeys> {
540 Err(ErrorKind::HttpStatus(reqwest::StatusCode::SERVICE_UNAVAILABLE).into())
541 }
542
543 fn verifier(
544 results: Vec<crate::error::Result<IdTokenKeys>>,
545 ) -> (IdTokenVerifier, Arc<AtomicUsize>) {
546 let (source, fetches) = ScriptedKeys::new(results);
547 (
548 IdTokenVerifier::with_keys_source(IdTokenAudience::new(AUDIENCE), source),
549 fetches,
550 )
551 }
552
553 #[tokio::test]
554 async fn valid_token_yields_its_claims() {
555 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
556 let claims = claims();
557
558 let verified = verifier
559 .verify(&signed_jwt(CURRENT_KEY, &claims))
560 .await
561 .unwrap();
562
563 assert_eq!(verified.audience(), &IdTokenAudience::new(AUDIENCE));
564 assert_eq!(verified.subject(), "112233445566778899");
565 assert_eq!(
566 verified.verified_email(),
567 Some(&PrincipalEmail::new(CALLER))
568 );
569 assert_eq!(verified.email_verified(), Some(true));
570 assert_eq!(verified.hosted_domain(), None);
571 assert_eq!(
572 verified.expires_at().as_second(),
573 claims["exp"].as_i64().unwrap()
574 );
575 }
576
577 #[tokio::test]
578 async fn expired_token_is_invalid() {
579 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
580 let mut expired = claims();
581 expired["exp"] = (Timestamp::now() - SignedDuration::from_mins(5))
582 .as_second()
583 .into();
584
585 let err = verifier
586 .verify(&signed_jwt(CURRENT_KEY, &expired))
587 .await
588 .unwrap_err();
589
590 assert!(matches!(
591 err,
592 IdTokenVerifyError::InvalidToken(InvalidIdToken::Expired)
593 ));
594 }
595
596 #[tokio::test]
597 async fn token_for_another_audience_is_invalid() {
598 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
599 let mut other_audience = claims();
600 other_audience["aud"] = "https://billing-abc123-ew.a.run.app".into();
601
602 let err = verifier
603 .verify(&signed_jwt(CURRENT_KEY, &other_audience))
604 .await
605 .unwrap_err();
606
607 assert!(matches!(
608 err,
609 IdTokenVerifyError::InvalidToken(InvalidIdToken::WrongAudience)
610 ));
611 }
612
613 #[tokio::test]
614 async fn token_from_another_issuer_is_invalid() {
615 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
616 let mut other_issuer = claims();
617 other_issuer["iss"] = "https://issuer.example.com".into();
618
619 let err = verifier
620 .verify(&signed_jwt(CURRENT_KEY, &other_issuer))
621 .await
622 .unwrap_err();
623
624 assert!(matches!(
625 err,
626 IdTokenVerifyError::InvalidToken(InvalidIdToken::WrongIssuer)
627 ));
628 }
629
630 fn assert_invalid_algorithm(err: IdTokenVerifyError) {
631 assert!(matches!(
632 err,
633 IdTokenVerifyError::InvalidToken(InvalidIdToken::Malformed(ref error))
634 if matches!(error.kind(), jsonwebtoken::errors::ErrorKind::InvalidAlgorithm)
635 ));
636 }
637
638 #[tokio::test]
639 async fn token_signed_with_the_public_key_as_hmac_secret_is_invalid() {
640 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
641 let public_key = serde_json::to_vec(&test_jwk_set(&[CURRENT_KEY]).keys[0]).unwrap();
642 let header = jsonwebtoken::Header {
643 kid: Some(CURRENT_KEY.to_string()),
644 ..jsonwebtoken::Header::new(Algorithm::HS256)
645 };
646 let token = jsonwebtoken::encode(
647 &header,
648 &claims(),
649 &jsonwebtoken::EncodingKey::from_secret(&public_key),
650 )
651 .unwrap();
652
653 assert_invalid_algorithm(verifier.verify(&token).await.unwrap_err());
654 }
655
656 #[tokio::test]
657 async fn token_signed_with_another_rsa_algorithm_is_invalid() {
658 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
659 let header = jsonwebtoken::Header {
660 kid: Some(CURRENT_KEY.to_string()),
661 ..jsonwebtoken::Header::new(Algorithm::RS512)
662 };
663 let key = jsonwebtoken::EncodingKey::from_rsa_pem(
664 crate::test_support::TEST_RSA_PRIVATE_KEY.as_bytes(),
665 )
666 .unwrap();
667 let token = jsonwebtoken::encode(&header, &claims(), &key).unwrap();
668
669 assert_invalid_algorithm(verifier.verify(&token).await.unwrap_err());
670 }
671
672 #[tokio::test]
673 async fn token_without_a_key_id_is_invalid() {
674 let (verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY])]);
675 let key = jsonwebtoken::EncodingKey::from_rsa_pem(
676 crate::test_support::TEST_RSA_PRIVATE_KEY.as_bytes(),
677 )
678 .unwrap();
679 let token = jsonwebtoken::encode(
680 &jsonwebtoken::Header::new(Algorithm::RS256),
681 &claims(),
682 &key,
683 )
684 .unwrap();
685
686 let err = verifier.verify(&token).await.unwrap_err();
687
688 assert!(matches!(
689 err,
690 IdTokenVerifyError::InvalidToken(InvalidIdToken::UnknownKey(None))
691 ));
692 assert_eq!(fetches.load(Ordering::SeqCst), 0);
693 }
694
695 #[tokio::test]
696 async fn token_issued_in_the_future_is_not_yet_valid() {
697 let (verifier, _) = verifier(vec![keys(&[CURRENT_KEY])]);
698 let mut issued_later = claims();
699 issued_later["iat"] = (Timestamp::now() + SignedDuration::from_mins(5))
700 .as_second()
701 .into();
702
703 let err = verifier
704 .verify(&signed_jwt(CURRENT_KEY, &issued_later))
705 .await
706 .unwrap_err();
707
708 assert!(matches!(
709 err,
710 IdTokenVerifyError::InvalidToken(InvalidIdToken::NotYetValid)
711 ));
712 }
713
714 #[tokio::test]
715 async fn unknown_key_refetches_the_keys() {
716 let (mut verifier, fetches) = verifier(vec![
717 keys(&[CURRENT_KEY]),
718 keys(&[CURRENT_KEY, ROTATED_KEY]),
719 ]);
720 verifier.refetch_interval = Duration::ZERO;
721
722 verifier
723 .verify(&signed_jwt(CURRENT_KEY, &claims()))
724 .await
725 .unwrap();
726 verifier
727 .verify(&signed_jwt(ROTATED_KEY, &claims()))
728 .await
729 .unwrap();
730
731 assert_eq!(fetches.load(Ordering::SeqCst), 2);
732 }
733
734 #[tokio::test]
735 async fn unknown_key_inside_the_refetch_interval_is_invalid() {
736 let (verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY])]);
737
738 verifier
739 .verify(&signed_jwt(CURRENT_KEY, &claims()))
740 .await
741 .unwrap();
742 let err = verifier
743 .verify(&signed_jwt(ROTATED_KEY, &claims()))
744 .await
745 .unwrap_err();
746
747 assert!(matches!(
748 err,
749 IdTokenVerifyError::InvalidToken(InvalidIdToken::UnknownKey(Some(ref kid))) if kid == ROTATED_KEY
750 ));
751 assert_eq!(fetches.load(Ordering::SeqCst), 1);
752 }
753
754 #[tokio::test]
755 async fn verification_during_a_refetch_waits_for_its_keys() {
756 let (mut source, fetches) = ScriptedKeys::new(vec![
757 keys(&[CURRENT_KEY]),
758 keys(&[CURRENT_KEY, ROTATED_KEY]),
759 ]);
760 source.fetch_duration = Duration::from_millis(300);
761 let mut verifier =
762 IdTokenVerifier::with_keys_source(IdTokenAudience::new(AUDIENCE), source);
763 verifier.refetch_interval = Duration::from_millis(100);
764 let verifier = Arc::new(verifier);
765 verifier
766 .verify(&signed_jwt(CURRENT_KEY, &claims()))
767 .await
768 .unwrap();
769 tokio::time::sleep(Duration::from_millis(150)).await;
770
771 let refetching = tokio::spawn({
772 let verifier = Arc::clone(&verifier);
773 async move { verifier.verify(&signed_jwt(ROTATED_KEY, &claims())).await }
774 });
775 tokio::time::sleep(Duration::from_millis(50)).await;
776 let waiting = verifier.verify(&signed_jwt(ROTATED_KEY, &claims())).await;
777
778 assert!(refetching.await.unwrap().is_ok());
779 assert!(waiting.is_ok(), "{waiting:?}");
780 assert_eq!(fetches.load(Ordering::SeqCst), 2);
781 }
782
783 #[tokio::test]
784 async fn cached_keys_stay_in_use_after_a_failed_refetch() {
785 let (mut verifier, fetches) = verifier(vec![keys(&[CURRENT_KEY]), unavailable()]);
786 verifier.refetch_interval = Duration::ZERO;
787
788 verifier
789 .verify(&signed_jwt(CURRENT_KEY, &claims()))
790 .await
791 .unwrap();
792 let err = verifier
793 .verify(&signed_jwt(ROTATED_KEY, &claims()))
794 .await
795 .unwrap_err();
796 verifier
797 .verify(&signed_jwt(CURRENT_KEY, &claims()))
798 .await
799 .unwrap();
800
801 assert!(matches!(err, IdTokenVerifyError::KeysUnavailable(_)));
802 assert_eq!(fetches.load(Ordering::SeqCst), 2);
803 }
804
805 #[tokio::test]
806 async fn keys_are_fetched_again_after_their_max_age() {
807 let (verifier, fetches) = verifier(vec![
808 Ok(IdTokenKeys::new(
809 test_jwk_set(&[CURRENT_KEY]),
810 Duration::ZERO,
811 )),
812 keys(&[CURRENT_KEY]),
813 ]);
814
815 verifier
816 .verify(&signed_jwt(CURRENT_KEY, &claims()))
817 .await
818 .unwrap();
819 verifier
820 .verify(&signed_jwt(CURRENT_KEY, &claims()))
821 .await
822 .unwrap();
823
824 assert_eq!(fetches.load(Ordering::SeqCst), 2);
825 }
826
827 #[test]
828 fn cache_max_age_directive_is_case_insensitive() {
829 let mut headers = reqwest::header::HeaderMap::new();
830 headers.insert(
831 reqwest::header::CACHE_CONTROL,
832 "public, MAX-AGE=600".parse().unwrap(),
833 );
834
835 assert_eq!(cache_max_age(&headers), Some(Duration::from_secs(600)));
836 }
837
838 #[tokio::test]
839 async fn google_keys_last_for_the_cache_max_age() {
840 let keys_endpoint = StubServer::start(vec![StubResponse::json(
841 "200 OK",
842 serde_json::to_string(&test_jwk_set(&[CURRENT_KEY])).unwrap(),
843 )
844 .with_header(
845 "cache-control",
846 "public, max-age=24948, must-revalidate, no-transform",
847 )])
848 .await;
849 let google_keys = GoogleKeys {
850 client: reqwest::Client::new(),
851 url: format!("{}/oauth2/v3/certs", keys_endpoint.url),
852 };
853
854 let fetched = google_keys.fetch_keys().await.unwrap();
855
856 assert_eq!(fetched.max_age, Duration::from_secs(24948));
857 assert!(fetched.keys.find(CURRENT_KEY).is_some());
858 assert_eq!(
859 keys_endpoint.received()[0].request_line,
860 "GET /oauth2/v3/certs HTTP/1.1"
861 );
862 }
863}