1use crate::token_verifier::jwt_cache::JwtCache;
2use async_lock::RwLock;
3use async_trait::async_trait;
4use reqwest::{header::AUTHORIZATION, StatusCode};
5use rust_mcp_sdk::{
6 auth::{
7 decode_token_header, Audience, AuthInfo, AuthenticationError, IntrospectionResponse,
8 JsonWebKeySet, OauthTokenVerifier,
9 },
10 mcp_http::error_message_from_response,
11};
12use serde_json::Value;
13use std::{
14 collections::HashMap,
15 time::{Duration, SystemTime},
16};
17use url::Url;
18
19const JWKS_REFRESH_TIME: Duration = Duration::from_secs(24 * 60 * 60); const REMOTE_VERIFICATION_INTERVAL: Duration = Duration::from_secs(15 * 60); const JWT_CACHE_CAPACITY: usize = 1000;
22
23struct JwksCache {
24 last_updated: Option<SystemTime>,
25 jwks: JsonWebKeySet,
26}
27
28pub enum VerificationStrategies {
34 Introspection {
40 introspection_uri: String,
42 client_id: String,
44 client_secret: String,
46 use_basic_auth: bool,
50 extra_params: Option<Vec<(&'static str, &'static str)>>,
54 },
55 JWKs {
61 jwks_uri: String,
63 },
64 UserInfo { userinfo_uri: String },
70}
71
72pub struct TokenVerifierOptions {
78 pub strategies: Vec<VerificationStrategies>,
83 pub validate_audience: Option<Audience>,
85 pub validate_issuer: Option<String>,
87 pub cache_capacity: Option<usize>,
89}
90
91#[derive(Default, Debug)]
92struct StrategiesOptions {
93 pub introspection_uri: Option<Url>,
94 pub introspection_basic_auth: bool,
95 pub introspect_extra_params: Option<Vec<(&'static str, &'static str)>>,
96 pub client_id: Option<String>,
97 pub client_secret: Option<String>,
98 pub jwks_uri: Option<Url>,
99 pub userinfo_uri: Option<Url>,
100}
101
102impl TokenVerifierOptions {
103 fn unpack(&mut self) -> Result<(StrategiesOptions, bool), AuthenticationError> {
104 let mut result = StrategiesOptions::default();
105
106 let mut has_jwks = false;
107 let mut has_other = false;
108
109 for strategy in self.strategies.drain(0..) {
110 match strategy {
111 VerificationStrategies::Introspection {
112 introspection_uri,
113 client_id,
114 client_secret,
115 use_basic_auth,
116 extra_params,
117 } => {
118 result.introspection_uri =
119 Some(Url::parse(&introspection_uri).map_err(|err| {
120 AuthenticationError::ParsingError(format!(
121 "Invalid introspection uri: {err}",
122 ))
123 })?);
124 result.client_id = Some(client_id);
125 result.client_secret = Some(client_secret);
126 result.introspection_basic_auth = use_basic_auth;
127 result.introspect_extra_params = extra_params;
128 has_other = true;
129 }
130 VerificationStrategies::JWKs { jwks_uri } => {
131 result.jwks_uri = Some(Url::parse(&jwks_uri).map_err(|err| {
132 AuthenticationError::ParsingError(format!("Invalid jwks uri: {err}"))
133 })?);
134 has_jwks = true;
135 }
136 VerificationStrategies::UserInfo { userinfo_uri } => {
137 result.userinfo_uri = Some(Url::parse(&userinfo_uri).map_err(|err| {
138 AuthenticationError::ParsingError(format!("Invalid userinfo uri: {err}"))
139 })?);
140 has_other = true;
141 }
142 }
143 }
144
145 Ok((result, has_jwks && has_other))
146 }
147}
148
149pub struct GenericOauthTokenVerifier {
150 validate_audience: Option<Audience>,
152 validate_issuer: Option<String>,
154 jwt_cache: Option<RwLock<JwtCache>>,
155 json_web_key_set: RwLock<Option<JwksCache>>,
156 introspection_uri: Option<Url>,
157 introspection_basic_auth: bool,
158 introspect_extra_params: Option<Vec<(&'static str, &'static str)>>,
159 client_id: Option<String>,
160 client_secret: Option<String>,
161 jwks_uri: Option<Url>,
162 userinfo_uri: Option<Url>,
163}
164
165impl GenericOauthTokenVerifier {
166 pub fn new(mut options: TokenVerifierOptions) -> Result<Self, AuthenticationError> {
167 let (strategy_options, chachable) = options.unpack()?;
168
169 let validate_audience = options.validate_audience.take();
170
171 let validate_issuer = options
172 .validate_issuer
173 .map(|iss| iss.trim_end_matches('/').to_string());
174
175 let jwt_cache = if chachable {
177 Some(RwLock::new(JwtCache::new(
178 REMOTE_VERIFICATION_INTERVAL,
179 options.cache_capacity.unwrap_or(JWT_CACHE_CAPACITY),
180 )))
181 } else {
182 None
183 };
184
185 Ok(Self {
186 validate_issuer,
187 validate_audience,
188 jwt_cache,
189 json_web_key_set: RwLock::new(None),
190 introspection_uri: strategy_options.introspection_uri,
191 introspection_basic_auth: strategy_options.introspection_basic_auth,
192 introspect_extra_params: strategy_options.introspect_extra_params,
193 client_id: strategy_options.client_id,
194 client_secret: strategy_options.client_secret,
195 jwks_uri: strategy_options.jwks_uri,
196 userinfo_uri: strategy_options.userinfo_uri,
197 })
198 }
199
200 async fn verify_user_info(
201 &self,
202 token: &str,
203 token_unique_id: Option<&str>,
204 user_info_endpoint: &Url,
205 ) -> Result<AuthInfo, AuthenticationError> {
206 let token_unique_id = match token_unique_id {
208 Some(id) => id.to_owned(),
209 None => {
210 let header = decode_token_header(token)?;
211 header.kid.unwrap_or(token.to_string()).to_owned()
212 }
213 };
214
215 let client = reqwest::Client::new();
216
217 let response = client
218 .get(user_info_endpoint.to_owned())
219 .header(AUTHORIZATION, format!("Bearer {token}"))
220 .send()
221 .await
222 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
223
224 let status_code = response.status();
225
226 if !response.status().is_success() {
227 return Err(AuthenticationError::TokenVerificationFailed {
228 description: error_message_from_response(response, "Unauthorized!").await,
229 status_code: Some(status_code.as_u16()),
230 });
231 }
232
233 let json: Value = response.json().await.unwrap();
234
235 let extra = match json {
236 Value::Object(map) => Some(map),
237 _ => None,
238 };
239
240 let auth_info: AuthInfo = AuthInfo {
241 token_unique_id,
242 client_id: None,
243 user_id: None,
244 scopes: None,
245 expires_at: None,
246 audience: None,
247 extra,
248 };
249
250 Ok(auth_info)
251 }
252
253 async fn verify_introspection(
254 &self,
255 token: &str,
256 introspection_endpoint: &Url,
257 ) -> Result<AuthInfo, AuthenticationError> {
258 let client = reqwest::Client::new();
259
260 let mut form = HashMap::new();
262 form.insert("token", token);
263
264 if !self.introspection_basic_auth {
265 if let Some(client_id) = self.client_id.as_ref() {
266 form.insert("client_id", client_id);
267 };
268 if let Some(client_secret) = self.client_secret.as_ref() {
269 form.insert("client_secret", client_secret);
270 };
271 }
272
273 if let Some(extra_params) = self.introspect_extra_params.as_ref() {
274 extra_params.iter().for_each(|(key, value)| {
275 form.insert(key, value);
276 });
277 }
278
279 let mut request = client.post(introspection_endpoint.to_owned()).form(&form);
280 if self.introspection_basic_auth {
281 request = request.basic_auth(
282 self.client_id.clone().unwrap_or_default(),
283 self.client_secret.clone(),
284 );
285 }
286
287 let response = request
288 .send()
289 .await
290 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
291
292 let status_code = response.status();
293 if !response.status().is_success() {
294 let description = response.text().await.unwrap_or("Unauthorized!".to_string());
295 return Err(AuthenticationError::TokenVerificationFailed {
296 description,
297 status_code: Some(status_code.as_u16()),
298 });
299 }
300
301 let introspect_response: IntrospectionResponse = response
302 .json()
303 .await
304 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
305
306 if !introspect_response.active {
307 return Err(AuthenticationError::InactiveToken);
308 }
309
310 if let Some(validate_audience) = self.validate_audience.as_ref() {
311 let Some(token_audience) = introspect_response.audience.as_ref() else {
312 return Err(AuthenticationError::InvalidToken {
313 description: "Audience attribute (aud) is missing.",
314 });
315 };
316
317 if token_audience != validate_audience {
318 return Err(AuthenticationError::TokenVerificationFailed { description:
319 format!("None of the provided audiences are allowed. Expected ${validate_audience}, got: ${token_audience}")
320 , status_code: Some(StatusCode::UNAUTHORIZED.as_u16())
321 });
322 }
323 }
324
325 if let Some(validate_issuer) = self.validate_issuer.as_ref() {
326 let Some(token_issuer) = introspect_response.issuer.as_ref() else {
327 return Err(AuthenticationError::InvalidToken {
328 description: "Issuer (iss) is missing.",
329 });
330 };
331
332 if token_issuer != validate_issuer {
333 return Err(AuthenticationError::TokenVerificationFailed {
334 description: format!(
335 "Issuer is not allowed. Expected ${validate_issuer}, got: ${token_issuer}"
336 ),
337 status_code: Some(StatusCode::UNAUTHORIZED.as_u16()),
338 });
339 }
340 }
341
342 AuthInfo::from_introspection_response(token.to_owned(), introspect_response, None)
343 }
344
345 async fn populate_jwks(&self, jwks_uri: &Url) -> Result<(), AuthenticationError> {
346 let response = reqwest::get(jwks_uri.to_owned())
347 .await
348 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
349 let jwks: JsonWebKeySet = response
350 .json()
351 .await
352 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
353 let mut guard = self.json_web_key_set.write().await;
354 *guard = Some(JwksCache {
355 last_updated: Some(SystemTime::now()),
356 jwks,
357 });
358 Ok(())
359 }
360
361 async fn verify_jwks(&self, token: &str, jwks: &Url) -> Result<AuthInfo, AuthenticationError> {
362 {
364 let guard = self.json_web_key_set.read().await;
365 if let Some(cache) = guard.as_ref() {
366 if let Some(last_updated) = cache.last_updated {
367 if SystemTime::now()
368 .duration_since(last_updated)
369 .unwrap_or(Duration::from_secs(0))
370 < JWKS_REFRESH_TIME
371 {
372 let token_info = cache.jwks.verify(
373 token.to_string(),
374 self.validate_audience.as_ref(),
375 self.validate_issuer.as_ref(),
376 )?;
377
378 return AuthInfo::from_token_data(token.to_owned(), token_info, None);
379 }
380 }
381 }
382 }
383
384 self.populate_jwks(jwks).await?;
386
387 let guard = self.json_web_key_set.read().await;
389 if let Some(cache) = guard.as_ref() {
390 let token_info = cache.jwks.verify(
391 token.to_string(),
392 self.validate_audience.as_ref(),
393 self.validate_issuer.as_ref(),
394 )?;
395
396 AuthInfo::from_token_data(token.to_owned(), token_info, None)
397 } else {
398 Err(AuthenticationError::Jwks(
399 "Failed to retrieve or parse JWKS".to_string(),
400 ))
401 }
402 }
403}
404
405#[async_trait]
406impl OauthTokenVerifier for GenericOauthTokenVerifier {
407 async fn verify_token(&self, access_token: String) -> Result<AuthInfo, AuthenticationError> {
408 if let Some(jwks_endpoint) = self.jwks_uri.as_ref() {
410 let mut auth_info = self.verify_jwks(&access_token, jwks_endpoint).await?;
411
412 if let Some(jwt_cache) = self.jwt_cache.as_ref() {
414 if jwt_cache.read().await.is_recent(&auth_info.token_unique_id) {
416 return Ok(auth_info);
417 }
418
419 if let Some(introspection_endpoint) = self.introspection_uri.as_ref() {
421 let fresh_auth_info = self
422 .verify_introspection(&access_token, introspection_endpoint)
423 .await?;
424 jwt_cache
425 .write()
426 .await
427 .record(fresh_auth_info.token_unique_id.to_owned());
428 return Ok(fresh_auth_info);
429 }
430
431 if let Some(user_info_endpoint) = self.userinfo_uri.as_ref() {
433 let fresh_auth_info = self
434 .verify_user_info(
435 &access_token,
436 Some(&auth_info.token_unique_id),
437 user_info_endpoint,
438 )
439 .await?;
440
441 auth_info.extra = fresh_auth_info.extra;
442 jwt_cache
443 .write()
444 .await
445 .record(auth_info.token_unique_id.to_owned());
446 return Ok(auth_info);
447 }
448 }
449
450 return Ok(auth_info);
451 }
452
453 if let Some(introspection_endpoint) = self.introspection_uri.as_ref() {
455 let auth_info = self
456 .verify_introspection(&access_token, introspection_endpoint)
457 .await?;
458 return Ok(auth_info);
459 }
460
461 if let Some(user_info_endpoint) = self.userinfo_uri.as_ref() {
463 let auth_info = self
464 .verify_user_info(&access_token, None, user_info_endpoint)
465 .await?;
466 return Ok(auth_info);
467 }
468
469 Err(AuthenticationError::InvalidToken {
470 description: "Invalid token verification strategy!",
471 })
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use oauth2_test_server::{OAuthTestServer, OauthEndpoints};
479 use rust_mcp_sdk::auth::*;
480 use serde_json::json;
481
482 async fn token_verifier(
483 strategies: Vec<VerificationStrategies>,
484 endpoints: &OauthEndpoints,
485 audience: Option<Audience>,
486 ) -> GenericOauthTokenVerifier {
487 let auth_metadata = AuthMetadataBuilder::new("http://127.0.0.1:3000/mcp")
488 .issuer(&endpoints.oauth_server)
489 .authorization_servers(vec![&endpoints.oauth_server])
490 .authorization_endpoint(&endpoints.authorize)
491 .token_endpoint(&endpoints.token)
492 .scopes_supported(vec!["openid".to_string()])
493 .introspection_endpoint(&endpoints.introspect)
494 .jwks_uri(&endpoints.jwks)
495 .resource_name("MCP Demo Server".to_string())
496 .build()
497 .unwrap();
498 let meta = &auth_metadata.0;
499
500 let token_verifier = GenericOauthTokenVerifier::new(TokenVerifierOptions {
501 validate_audience: audience,
502 validate_issuer: Some(meta.issuer.to_string()),
503 strategies,
504 cache_capacity: None,
505 })
506 .unwrap();
507 token_verifier
508 }
509
510 #[tokio::test]
511 async fn test_jwks_strategy() {
512 let server = OAuthTestServer::start().await;
513
514 let client = server.register_client(
515 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
516 );
517
518 let verifier = token_verifier(
519 vec![VerificationStrategies::JWKs {
520 jwks_uri: server.endpoints.jwks.clone(),
521 }],
522 &server.endpoints,
523 Some(Audience::Single(client.client_id.clone())),
524 )
525 .await;
526
527 let token = server.generate_jwt(&client, server.jwt_options().user_id("rustmcp").build());
528
529 let auth_info = verifier.verify_token(token).await.unwrap();
530 assert_eq!(
531 auth_info.audience.as_ref().unwrap().to_string(),
532 client.client_id
533 );
534 assert_eq!(
535 auth_info.client_id.as_ref().unwrap().to_string(),
536 client.client_id
537 );
538 assert_eq!(auth_info.user_id.as_ref().unwrap(), "rustmcp");
539 let scopes = auth_info.scopes.as_ref().unwrap();
540 assert_eq!(scopes.as_slice(), ["openid"]);
541 }
542
543 #[tokio::test]
544 async fn test_userinfo_strategy() {
545 let server = OAuthTestServer::start().await;
546
547 let client = server.register_client(
548 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
549 );
550
551 let verifier = token_verifier(
552 vec![VerificationStrategies::UserInfo {
553 userinfo_uri: server.endpoints.userinfo.clone(),
554 }],
555 &server.endpoints,
556 None,
557 )
558 .await;
559
560 let token = server.generate_token(&client, server.jwt_options().user_id("rustmcp").build());
561
562 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
563
564 assert!(auth_info.audience.is_none());
565 assert_eq!(
566 auth_info
567 .extra
568 .unwrap()
569 .get("sub")
570 .unwrap()
571 .as_str()
572 .unwrap(),
573 "rustmcp"
574 );
575 }
576
577 #[tokio::test]
578 async fn test_introspect_strategy() {
579 let server = OAuthTestServer::start().await;
580
581 let client = server.register_client(
582 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
583 );
584
585 let verifier = token_verifier(
586 vec![VerificationStrategies::Introspection {
587 introspection_uri: server.endpoints.introspect.clone(),
588 client_id: client.client_id.clone(),
589 client_secret: client.client_secret.as_ref().unwrap().clone(),
590 use_basic_auth: true,
591 extra_params: None,
592 }],
593 &server.endpoints,
594 None,
595 )
596 .await;
597
598 let token = server.generate_token(&client, server.jwt_options().user_id("rustmcp").build());
599 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
600
601 assert_eq!(
602 auth_info.audience.as_ref().unwrap().to_string(),
603 client.client_id
604 );
605 assert_eq!(
606 auth_info.client_id.as_ref().unwrap().to_string(),
607 client.client_id
608 );
609 assert_eq!(auth_info.user_id.as_ref().unwrap(), "rustmcp");
610 let scopes = auth_info.scopes.as_ref().unwrap();
611 assert_eq!(scopes.as_slice(), ["openid"]);
612 }
613
614 #[tokio::test]
615 async fn test_introspect_strategy_with_client_secret_post() {
616 let server = OAuthTestServer::start().await;
617
618 let client = server.register_client(
619 json!({ "scope": "openid profile", "redirect_uris":["http://localhost:8080/cb"]}),
620 );
621
622 let verifier = token_verifier(
623 vec![VerificationStrategies::Introspection {
624 introspection_uri: server.endpoints.introspect.clone(),
625 client_id: client.client_id.clone(),
626 client_secret: client.client_secret.as_ref().unwrap().clone(),
627 use_basic_auth: false, extra_params: None,
629 }],
630 &server.endpoints,
631 Some(Audience::Single(client.client_id.clone())),
632 )
633 .await;
634
635 let token = server.generate_token(&client, server.jwt_options().user_id("alice").build());
636
637 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
638
639 assert_eq!(auth_info.user_id.as_ref().unwrap(), "alice");
640 assert!(auth_info.scopes.unwrap().contains(&"profile".to_string()));
641 assert_eq!(
642 auth_info.audience.as_ref().unwrap().to_string(),
643 client.client_id
644 );
645 }
646
647 #[tokio::test]
648 async fn test_introspect_rejects_inactive_token() {
649 let server = OAuthTestServer::start().await;
650 let client = server
651 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }));
652
653 let verifier = token_verifier(
654 vec![VerificationStrategies::Introspection {
655 introspection_uri: server.endpoints.introspect.clone(),
656 client_id: client.client_id.clone(),
657 client_secret: client.client_secret.as_ref().unwrap().clone(),
658 use_basic_auth: true,
659 extra_params: None,
660 }],
661 &server.endpoints,
662 None,
663 )
664 .await;
665
666 let token_response =
667 server.generate_token(&client, server.jwt_options().user_id("bob").build());
668 server
669 .revoke_token(&client, &token_response.access_token)
670 .await;
671
672 let result = verifier.verify_token(token_response.access_token).await;
673 assert!(matches!(result, Err(AuthenticationError::InactiveToken)));
674 }
675
676 #[tokio::test]
677 async fn test_expired_token_rejected_by_jwks_and_introspection() {
678 let server = OAuthTestServer::start().await;
679 let client = server.register_client(
680 json!({ "scope": "openid email", "redirect_uris": ["http://localhost"] }),
681 );
682
683 let verifier = token_verifier(
685 vec![
686 VerificationStrategies::JWKs {
687 jwks_uri: server.endpoints.jwks.clone(),
688 },
689 VerificationStrategies::Introspection {
690 introspection_uri: server.endpoints.introspect.clone(),
691 client_id: client.client_id.clone(),
692 client_secret: client.client_secret.as_ref().unwrap().clone(),
693 use_basic_auth: true,
694 extra_params: None,
695 },
696 ],
697 &server.endpoints,
698 Some(Audience::Single(client.client_id.clone())),
699 )
700 .await;
701
702 let short_lived = server
704 .jwt_options()
705 .user_id("charlie")
706 .expires_in(1)
707 .build();
708 let token = server.generate_token(&client, short_lived);
709
710 tokio::time::sleep(tokio::time::Duration::from_millis(1500)).await;
712
713 let err1 = verifier
716 .verify_token(token.access_token.clone())
717 .await
718 .unwrap_err();
719 assert!(matches!(err1, AuthenticationError::InactiveToken));
720
721 server.revoke_token(&client, &token.access_token).await;
723 let err2 = verifier.verify_token(token.access_token).await.unwrap_err();
724 assert!(matches!(err2, AuthenticationError::InactiveToken));
725 }
726
727 #[tokio::test]
728 async fn test_jwks_and_introspection_cache_works() {
729 let server = OAuthTestServer::start().await;
730 let client = server
731 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }));
732
733 let verifier = token_verifier(
734 vec![
735 VerificationStrategies::JWKs {
736 jwks_uri: server.endpoints.jwks.clone(),
737 },
738 VerificationStrategies::Introspection {
739 introspection_uri: server.endpoints.introspect.clone(),
740 client_id: client.client_id.clone(),
741 client_secret: client.client_secret.as_ref().unwrap().clone(),
742 use_basic_auth: true,
743 extra_params: None,
744 },
745 ],
746 &server.endpoints,
747 None,
748 )
749 .await;
750
751 let token = server.generate_token(&client, server.jwt_options().user_id("dave").build());
752
753 let info1 = verifier
755 .verify_token(token.access_token.clone())
756 .await
757 .unwrap();
758
759 let info2 = verifier
761 .verify_token(token.access_token.clone())
762 .await
763 .unwrap();
764
765 assert_eq!(info1.user_id, info2.user_id);
766 assert_eq!(info1.token_unique_id, info2.token_unique_id);
767 }
768
769 #[tokio::test]
770 async fn test_audience_validation_rejects_wrong_aud() {
771 let server = OAuthTestServer::start().await;
772 let client = server
773 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }));
774
775 let verifier = token_verifier(
776 vec![VerificationStrategies::Introspection {
777 introspection_uri: server.endpoints.introspect.clone(),
778 client_id: client.client_id.clone(),
779 client_secret: client.client_secret.as_ref().unwrap().clone(),
780 use_basic_auth: true,
781 extra_params: None,
782 }],
783 &server.endpoints,
784 Some(Audience::Single("wrong-client-id-999".to_string())),
785 )
786 .await;
787
788 let token = server.generate_token(&client, server.jwt_options().user_id("eve").build());
789
790 let err = verifier.verify_token(token.access_token).await.unwrap_err();
791 assert!(matches!(
792 err,
793 AuthenticationError::TokenVerificationFailed { .. }
794 ));
795 }
796
797 #[tokio::test]
798 async fn test_issuer_validation_rejects_wrong_iss() {
799 let server = OAuthTestServer::start().await;
800 let client = server
801 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }));
802
803 let _verifier = token_verifier(
804 vec![VerificationStrategies::JWKs {
805 jwks_uri: server.endpoints.jwks.clone(),
806 }],
807 &server.endpoints,
808 None,
809 )
810 .await;
811
812 let wrong_verifier = GenericOauthTokenVerifier::new(TokenVerifierOptions {
814 strategies: vec![VerificationStrategies::JWKs {
815 jwks_uri: server.endpoints.jwks.clone(),
816 }],
817 validate_audience: None,
818 validate_issuer: Some("https://wrong-issuer.example.com".to_string()),
819 cache_capacity: None,
820 })
821 .unwrap();
822
823 let token = server.generate_token(&client, server.jwt_options().user_id("frank").build());
824
825 let err = wrong_verifier
826 .verify_token(token.access_token)
827 .await
828 .unwrap_err();
829 assert!(matches!(
830 err,
831 AuthenticationError::TokenVerificationFailed { .. }
832 ));
833 }
834
835 #[tokio::test]
836 async fn test_userinfo_enriches_jwt_claims() {
837 let server = OAuthTestServer::start().await;
838 let client = server.register_client(
839 json!({ "scope": "openid profile email", "redirect_uris": ["http://localhost"] }),
840 );
841
842 let verifier = token_verifier(
843 vec![
844 VerificationStrategies::JWKs {
845 jwks_uri: server.endpoints.jwks.clone(),
846 },
847 VerificationStrategies::UserInfo {
848 userinfo_uri: server.endpoints.userinfo.clone(),
849 },
850 ],
851 &server.endpoints,
852 None,
853 )
854 .await;
855
856 let token = server.generate_token(&client, server.jwt_options().user_id("grace").build());
857
858 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
859
860 let extra = auth_info.extra.unwrap();
861 assert_eq!(
862 extra.get("email").unwrap().as_str().unwrap(),
863 "test@example.com"
864 );
865 assert_eq!(extra.get("name").unwrap().as_str().unwrap(), "Test User");
866 assert!(extra.get("picture").is_some());
867 }
868}