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, default_jwks_algorithms, shared_http_client, Algorithm, Audience,
8 AuthInfo, AuthenticationError, IntrospectionResponse, 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 allowed_algorithms: Vec<Algorithm>,
156 jwt_cache: Option<RwLock<JwtCache>>,
157 json_web_key_set: RwLock<Option<JwksCache>>,
158 introspection_uri: Option<Url>,
159 introspection_basic_auth: bool,
160 introspect_extra_params: Option<Vec<(&'static str, &'static str)>>,
161 client_id: Option<String>,
162 client_secret: Option<String>,
163 jwks_uri: Option<Url>,
164 userinfo_uri: Option<Url>,
165}
166
167impl GenericOauthTokenVerifier {
168 pub fn new(mut options: TokenVerifierOptions) -> Result<Self, AuthenticationError> {
169 let (strategy_options, chachable) = options.unpack()?;
170
171 let validate_audience = options.validate_audience.take();
172
173 let validate_issuer = options
174 .validate_issuer
175 .map(|iss| iss.trim_end_matches('/').to_string());
176
177 let jwt_cache = if chachable {
179 Some(RwLock::new(JwtCache::new(
180 REMOTE_VERIFICATION_INTERVAL,
181 options.cache_capacity.unwrap_or(JWT_CACHE_CAPACITY),
182 )))
183 } else {
184 None
185 };
186
187 Ok(Self {
188 validate_issuer,
189 validate_audience,
190 allowed_algorithms: default_jwks_algorithms(),
191 jwt_cache,
192 json_web_key_set: RwLock::new(None),
193 introspection_uri: strategy_options.introspection_uri,
194 introspection_basic_auth: strategy_options.introspection_basic_auth,
195 introspect_extra_params: strategy_options.introspect_extra_params,
196 client_id: strategy_options.client_id,
197 client_secret: strategy_options.client_secret,
198 jwks_uri: strategy_options.jwks_uri,
199 userinfo_uri: strategy_options.userinfo_uri,
200 })
201 }
202
203 pub fn with_allowed_algorithms(mut self, algorithms: Vec<Algorithm>) -> Self {
208 self.allowed_algorithms = algorithms;
209 self
210 }
211
212 async fn verify_user_info(
213 &self,
214 token: &str,
215 token_unique_id: Option<&str>,
216 user_info_endpoint: &Url,
217 ) -> Result<AuthInfo, AuthenticationError> {
218 let token_unique_id = match token_unique_id {
220 Some(id) => id.to_owned(),
221 None => {
222 let header = decode_token_header(token)?;
223 header.kid.unwrap_or(token.to_string()).to_owned()
224 }
225 };
226
227 let client = shared_http_client();
228
229 let response = client
230 .get(user_info_endpoint.to_owned())
231 .header(AUTHORIZATION, format!("Bearer {token}"))
232 .send()
233 .await
234 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
235
236 let status_code = response.status();
237
238 if !response.status().is_success() {
239 return Err(AuthenticationError::TokenVerificationFailed {
240 description: error_message_from_response(response, "Unauthorized!").await,
241 status_code: Some(status_code.as_u16()),
242 });
243 }
244
245 let json: Value = response.json().await.unwrap();
246
247 let extra = match json {
248 Value::Object(map) => Some(map),
249 _ => None,
250 };
251
252 let auth_info: AuthInfo = AuthInfo {
253 token_unique_id,
254 client_id: None,
255 user_id: None,
256 scopes: None,
257 expires_at: None,
258 audience: None,
259 extra,
260 };
261
262 Ok(auth_info)
263 }
264
265 async fn verify_introspection(
266 &self,
267 token: &str,
268 introspection_endpoint: &Url,
269 ) -> Result<AuthInfo, AuthenticationError> {
270 let client = shared_http_client();
271
272 let mut form = HashMap::new();
274 form.insert("token", token);
275
276 if !self.introspection_basic_auth {
277 if let Some(client_id) = self.client_id.as_ref() {
278 form.insert("client_id", client_id);
279 };
280 if let Some(client_secret) = self.client_secret.as_ref() {
281 form.insert("client_secret", client_secret);
282 };
283 }
284
285 if let Some(extra_params) = self.introspect_extra_params.as_ref() {
286 extra_params.iter().for_each(|(key, value)| {
287 form.insert(key, value);
288 });
289 }
290
291 let mut request = client.post(introspection_endpoint.to_owned()).form(&form);
292 if self.introspection_basic_auth {
293 request = request.basic_auth(
294 self.client_id.clone().unwrap_or_default(),
295 self.client_secret.clone(),
296 );
297 }
298
299 let response = request
300 .send()
301 .await
302 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
303
304 let status_code = response.status();
305 if !response.status().is_success() {
306 let description = response.text().await.unwrap_or("Unauthorized!".to_string());
307 return Err(AuthenticationError::TokenVerificationFailed {
308 description,
309 status_code: Some(status_code.as_u16()),
310 });
311 }
312
313 let introspect_response: IntrospectionResponse = response
314 .json()
315 .await
316 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
317
318 if !introspect_response.active {
319 return Err(AuthenticationError::InactiveToken);
320 }
321
322 if let Some(validate_audience) = self.validate_audience.as_ref() {
323 let Some(token_audience) = introspect_response.audience.as_ref() else {
324 return Err(AuthenticationError::InvalidToken {
325 description: "Audience attribute (aud) is missing.",
326 });
327 };
328
329 if token_audience != validate_audience {
330 return Err(AuthenticationError::TokenVerificationFailed { description:
331 format!("None of the provided audiences are allowed. Expected ${validate_audience}, got: ${token_audience}")
332 , status_code: Some(StatusCode::UNAUTHORIZED.as_u16())
333 });
334 }
335 }
336
337 if let Some(validate_issuer) = self.validate_issuer.as_ref() {
338 let Some(token_issuer) = introspect_response.issuer.as_ref() else {
339 return Err(AuthenticationError::InvalidToken {
340 description: "Issuer (iss) is missing.",
341 });
342 };
343
344 if token_issuer != validate_issuer {
345 return Err(AuthenticationError::TokenVerificationFailed {
346 description: format!(
347 "Issuer is not allowed. Expected ${validate_issuer}, got: ${token_issuer}"
348 ),
349 status_code: Some(StatusCode::UNAUTHORIZED.as_u16()),
350 });
351 }
352 }
353
354 AuthInfo::from_introspection_response(token.to_owned(), introspect_response, None)
355 }
356
357 async fn populate_jwks(&self, jwks_uri: &Url) -> Result<(), AuthenticationError> {
358 let response = shared_http_client()
359 .get(jwks_uri.to_owned())
360 .send()
361 .await
362 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
363 let jwks: JsonWebKeySet = response
364 .json()
365 .await
366 .map_err(|err| AuthenticationError::Jwks(err.to_string()))?;
367 let mut guard = self.json_web_key_set.write().await;
368 *guard = Some(JwksCache {
369 last_updated: Some(SystemTime::now()),
370 jwks,
371 });
372 Ok(())
373 }
374
375 async fn verify_jwks(&self, token: &str, jwks: &Url) -> Result<AuthInfo, AuthenticationError> {
376 {
378 let guard = self.json_web_key_set.read().await;
379 if let Some(cache) = guard.as_ref() {
380 if let Some(last_updated) = cache.last_updated {
381 if SystemTime::now()
382 .duration_since(last_updated)
383 .unwrap_or(Duration::from_secs(0))
384 < JWKS_REFRESH_TIME
385 {
386 let token_info = cache.jwks.verify(
387 token.to_string(),
388 &self.allowed_algorithms,
389 self.validate_audience.as_ref(),
390 self.validate_issuer.as_ref(),
391 )?;
392
393 return AuthInfo::from_token_data(token.to_owned(), token_info, None);
394 }
395 }
396 }
397 }
398
399 self.populate_jwks(jwks).await?;
401
402 let guard = self.json_web_key_set.read().await;
404 if let Some(cache) = guard.as_ref() {
405 let token_info = cache.jwks.verify(
406 token.to_string(),
407 &self.allowed_algorithms,
408 self.validate_audience.as_ref(),
409 self.validate_issuer.as_ref(),
410 )?;
411
412 AuthInfo::from_token_data(token.to_owned(), token_info, None)
413 } else {
414 Err(AuthenticationError::Jwks(
415 "Failed to retrieve or parse JWKS".to_string(),
416 ))
417 }
418 }
419}
420
421#[async_trait]
422impl OauthTokenVerifier for GenericOauthTokenVerifier {
423 async fn verify_token(&self, access_token: String) -> Result<AuthInfo, AuthenticationError> {
424 if let Some(jwks_endpoint) = self.jwks_uri.as_ref() {
426 let mut auth_info = self.verify_jwks(&access_token, jwks_endpoint).await?;
427
428 if let Some(jwt_cache) = self.jwt_cache.as_ref() {
430 if jwt_cache.read().await.is_recent(&auth_info.token_unique_id) {
432 return Ok(auth_info);
433 }
434
435 if let Some(introspection_endpoint) = self.introspection_uri.as_ref() {
437 let fresh_auth_info = self
438 .verify_introspection(&access_token, introspection_endpoint)
439 .await?;
440 jwt_cache
441 .write()
442 .await
443 .record(fresh_auth_info.token_unique_id.to_owned());
444 return Ok(fresh_auth_info);
445 }
446
447 if let Some(user_info_endpoint) = self.userinfo_uri.as_ref() {
449 let fresh_auth_info = self
450 .verify_user_info(
451 &access_token,
452 Some(&auth_info.token_unique_id),
453 user_info_endpoint,
454 )
455 .await?;
456
457 auth_info.extra = fresh_auth_info.extra;
458 jwt_cache
459 .write()
460 .await
461 .record(auth_info.token_unique_id.to_owned());
462 return Ok(auth_info);
463 }
464 }
465
466 return Ok(auth_info);
467 }
468
469 if let Some(introspection_endpoint) = self.introspection_uri.as_ref() {
471 let auth_info = self
472 .verify_introspection(&access_token, introspection_endpoint)
473 .await?;
474 return Ok(auth_info);
475 }
476
477 if let Some(user_info_endpoint) = self.userinfo_uri.as_ref() {
479 let auth_info = self
480 .verify_user_info(&access_token, None, user_info_endpoint)
481 .await?;
482 return Ok(auth_info);
483 }
484
485 Err(AuthenticationError::InvalidToken {
486 description: "Invalid token verification strategy!",
487 })
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use oauth2_test_server::{OAuthTestServer, OauthEndpoints};
495 use rust_mcp_sdk::auth::*;
496 use serde_json::json;
497
498 async fn token_verifier(
499 strategies: Vec<VerificationStrategies>,
500 endpoints: &OauthEndpoints,
501 audience: Option<Audience>,
502 ) -> GenericOauthTokenVerifier {
503 let auth_metadata = AuthMetadataBuilder::new("http://127.0.0.1:3000/mcp")
504 .issuer(&endpoints.oauth_server)
505 .authorization_servers(vec![&endpoints.oauth_server])
506 .authorization_endpoint(&endpoints.authorize)
507 .token_endpoint(&endpoints.token)
508 .scopes_supported(vec!["openid".to_string()])
509 .introspection_endpoint(&endpoints.introspect)
510 .jwks_uri(&endpoints.jwks)
511 .resource_name("MCP Demo Server".to_string())
512 .build()
513 .unwrap();
514 let meta = &auth_metadata.0;
515
516 let token_verifier = GenericOauthTokenVerifier::new(TokenVerifierOptions {
517 validate_audience: audience,
518 validate_issuer: Some(meta.issuer.to_string()),
519 strategies,
520 cache_capacity: None,
521 })
522 .unwrap();
523 token_verifier
524 }
525
526 #[tokio::test]
527 async fn test_jwks_strategy() {
528 let server = OAuthTestServer::start().await;
529
530 let client = server
531 .register_client(
532 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
533 )
534 .await;
535
536 let verifier = token_verifier(
537 vec![VerificationStrategies::JWKs {
538 jwks_uri: server.endpoints.jwks.clone(),
539 }],
540 &server.endpoints,
541 Some(Audience::Single(client.client_id.clone())),
542 )
543 .await;
544
545 let token = server.generate_jwt(&client, server.jwt_options().user_id("rustmcp").build());
546
547 let auth_info = verifier.verify_token(token).await.unwrap();
548 assert_eq!(
549 auth_info.audience.as_ref().unwrap().to_string(),
550 client.client_id
551 );
552 assert_eq!(
553 auth_info.client_id.as_ref().unwrap().to_string(),
554 client.client_id
555 );
556 assert_eq!(auth_info.user_id.as_ref().unwrap(), "rustmcp");
557 let scopes = auth_info.scopes.as_ref().unwrap();
558 assert_eq!(scopes.as_slice(), ["openid"]);
559 }
560
561 #[tokio::test]
562 async fn test_userinfo_strategy() {
563 let server = OAuthTestServer::start().await;
564
565 let client = server
566 .register_client(
567 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
568 )
569 .await;
570
571 let verifier = token_verifier(
572 vec![VerificationStrategies::UserInfo {
573 userinfo_uri: server.endpoints.userinfo.clone(),
574 }],
575 &server.endpoints,
576 None,
577 )
578 .await;
579
580 let token = server
581 .generate_token(&client, server.jwt_options().user_id("rustmcp").build())
582 .await;
583
584 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
585
586 assert!(auth_info.audience.is_none());
587 assert_eq!(
588 auth_info
589 .extra
590 .unwrap()
591 .get("sub")
592 .unwrap()
593 .as_str()
594 .unwrap(),
595 "rustmcp"
596 );
597 }
598
599 #[tokio::test]
600 async fn test_introspect_strategy() {
601 let server = OAuthTestServer::start().await;
602
603 let client = server
604 .register_client(
605 json!({ "scope": "openid", "redirect_uris":["http://localhost:8080/callback"]}),
606 )
607 .await;
608
609 let verifier = token_verifier(
610 vec![VerificationStrategies::Introspection {
611 introspection_uri: server.endpoints.introspect.clone(),
612 client_id: client.client_id.clone(),
613 client_secret: client.client_secret.as_ref().unwrap().clone(),
614 use_basic_auth: true,
615 extra_params: None,
616 }],
617 &server.endpoints,
618 None,
619 )
620 .await;
621
622 let token = server
623 .generate_token(&client, server.jwt_options().user_id("rustmcp").build())
624 .await;
625 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
626
627 assert_eq!(
628 auth_info.audience.as_ref().unwrap().to_string(),
629 client.client_id
630 );
631 assert_eq!(
632 auth_info.client_id.as_ref().unwrap().to_string(),
633 client.client_id
634 );
635 assert_eq!(auth_info.user_id.as_ref().unwrap(), "rustmcp");
636 let scopes = auth_info.scopes.as_ref().unwrap();
637 assert_eq!(scopes.as_slice(), ["openid"]);
638 }
639
640 #[tokio::test]
641 async fn test_introspect_strategy_with_client_secret_post() {
642 let server = OAuthTestServer::start().await;
643
644 let client = server
645 .register_client(
646 json!({ "scope": "openid profile", "redirect_uris":["http://localhost:8080/cb"]}),
647 )
648 .await;
649
650 let verifier = token_verifier(
651 vec![VerificationStrategies::Introspection {
652 introspection_uri: server.endpoints.introspect.clone(),
653 client_id: client.client_id.clone(),
654 client_secret: client.client_secret.as_ref().unwrap().clone(),
655 use_basic_auth: false, extra_params: None,
657 }],
658 &server.endpoints,
659 Some(Audience::Single(client.client_id.clone())),
660 )
661 .await;
662
663 let token = server
664 .generate_token(&client, server.jwt_options().user_id("alice").build())
665 .await;
666
667 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
668
669 assert_eq!(auth_info.user_id.as_ref().unwrap(), "alice");
670 assert!(auth_info.scopes.unwrap().contains(&"profile".to_string()));
671 assert_eq!(
672 auth_info.audience.as_ref().unwrap().to_string(),
673 client.client_id
674 );
675 }
676
677 #[tokio::test]
678 async fn test_introspect_rejects_inactive_token() {
679 let server = OAuthTestServer::start().await;
680 let client = server
681 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }))
682 .await;
683
684 let verifier = token_verifier(
685 vec![VerificationStrategies::Introspection {
686 introspection_uri: server.endpoints.introspect.clone(),
687 client_id: client.client_id.clone(),
688 client_secret: client.client_secret.as_ref().unwrap().clone(),
689 use_basic_auth: true,
690 extra_params: None,
691 }],
692 &server.endpoints,
693 None,
694 )
695 .await;
696
697 let token_response = server
698 .generate_token(&client, server.jwt_options().user_id("bob").build())
699 .await;
700 server
701 .revoke_token(&client, &token_response.access_token)
702 .await;
703
704 let result = verifier.verify_token(token_response.access_token).await;
705 assert!(matches!(result, Err(AuthenticationError::InactiveToken)));
706 }
707
708 #[tokio::test]
709 async fn test_expired_token_rejected_by_jwks_and_introspection() {
710 let server = OAuthTestServer::start().await;
711 let client = server
712 .register_client(
713 json!({ "scope": "openid email", "redirect_uris": ["http://localhost"] }),
714 )
715 .await;
716
717 let verifier = token_verifier(
719 vec![
720 VerificationStrategies::JWKs {
721 jwks_uri: server.endpoints.jwks.clone(),
722 },
723 VerificationStrategies::Introspection {
724 introspection_uri: server.endpoints.introspect.clone(),
725 client_id: client.client_id.clone(),
726 client_secret: client.client_secret.as_ref().unwrap().clone(),
727 use_basic_auth: true,
728 extra_params: None,
729 },
730 ],
731 &server.endpoints,
732 Some(Audience::Single(client.client_id.clone())),
733 )
734 .await;
735
736 let short_lived = server
738 .jwt_options()
739 .user_id("charlie")
740 .expires_in(1)
741 .build();
742 let token = server.generate_token(&client, short_lived).await;
743
744 tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await;
746
747 let err1 = verifier
750 .verify_token(token.access_token.clone())
751 .await
752 .unwrap_err();
753 assert!(matches!(err1, AuthenticationError::InactiveToken));
754
755 server.revoke_token(&client, &token.access_token).await;
757 let err2 = verifier.verify_token(token.access_token).await.unwrap_err();
758 assert!(matches!(err2, AuthenticationError::InactiveToken));
759 }
760
761 #[tokio::test]
762 async fn test_jwks_and_introspection_cache_works() {
763 let server = OAuthTestServer::start().await;
764 let client = server
765 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }))
766 .await;
767
768 let verifier = token_verifier(
769 vec![
770 VerificationStrategies::JWKs {
771 jwks_uri: server.endpoints.jwks.clone(),
772 },
773 VerificationStrategies::Introspection {
774 introspection_uri: server.endpoints.introspect.clone(),
775 client_id: client.client_id.clone(),
776 client_secret: client.client_secret.as_ref().unwrap().clone(),
777 use_basic_auth: true,
778 extra_params: None,
779 },
780 ],
781 &server.endpoints,
782 None,
783 )
784 .await;
785
786 let token = server
787 .generate_token(&client, server.jwt_options().user_id("dave").build())
788 .await;
789
790 let info1 = verifier
792 .verify_token(token.access_token.clone())
793 .await
794 .unwrap();
795
796 let info2 = verifier
798 .verify_token(token.access_token.clone())
799 .await
800 .unwrap();
801
802 assert_eq!(info1.user_id, info2.user_id);
803 assert_eq!(info1.token_unique_id, info2.token_unique_id);
804 }
805
806 #[tokio::test]
807 async fn test_audience_validation_rejects_wrong_aud() {
808 let server = OAuthTestServer::start().await;
809 let client = server
810 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }))
811 .await;
812
813 let verifier = token_verifier(
814 vec![VerificationStrategies::Introspection {
815 introspection_uri: server.endpoints.introspect.clone(),
816 client_id: client.client_id.clone(),
817 client_secret: client.client_secret.as_ref().unwrap().clone(),
818 use_basic_auth: true,
819 extra_params: None,
820 }],
821 &server.endpoints,
822 Some(Audience::Single("wrong-client-id-999".to_string())),
823 )
824 .await;
825
826 let token = server
827 .generate_token(&client, server.jwt_options().user_id("eve").build())
828 .await;
829
830 let err = verifier.verify_token(token.access_token).await.unwrap_err();
831 assert!(matches!(
832 err,
833 AuthenticationError::TokenVerificationFailed { .. }
834 ));
835 }
836
837 #[tokio::test]
838 async fn test_issuer_validation_rejects_wrong_iss() {
839 let server = OAuthTestServer::start().await;
840 let client = server
841 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }))
842 .await;
843
844 let _verifier = token_verifier(
845 vec![VerificationStrategies::JWKs {
846 jwks_uri: server.endpoints.jwks.clone(),
847 }],
848 &server.endpoints,
849 None,
850 )
851 .await;
852
853 let wrong_verifier = GenericOauthTokenVerifier::new(TokenVerifierOptions {
855 strategies: vec![VerificationStrategies::JWKs {
856 jwks_uri: server.endpoints.jwks.clone(),
857 }],
858 validate_audience: None,
859 validate_issuer: Some("https://wrong-issuer.example.com".to_string()),
860 cache_capacity: None,
861 })
862 .unwrap();
863
864 let token = server
865 .generate_token(&client, server.jwt_options().user_id("frank").build())
866 .await;
867
868 let err = wrong_verifier
869 .verify_token(token.access_token)
870 .await
871 .unwrap_err();
872 assert!(matches!(
873 err,
874 AuthenticationError::TokenVerificationFailed { .. }
875 ));
876 }
877
878 #[tokio::test]
879 async fn test_userinfo_enriches_jwt_claims() {
880 let server = OAuthTestServer::start().await;
881 let client = server
882 .register_client(
883 json!({ "scope": "openid profile email", "redirect_uris": ["http://localhost"] }),
884 )
885 .await;
886
887 let verifier = token_verifier(
888 vec![
889 VerificationStrategies::JWKs {
890 jwks_uri: server.endpoints.jwks.clone(),
891 },
892 VerificationStrategies::UserInfo {
893 userinfo_uri: server.endpoints.userinfo.clone(),
894 },
895 ],
896 &server.endpoints,
897 None,
898 )
899 .await;
900
901 let token = server
902 .generate_token(&client, server.jwt_options().user_id("grace").build())
903 .await;
904
905 let auth_info = verifier.verify_token(token.access_token).await.unwrap();
906
907 let extra = auth_info.extra.unwrap();
908 assert_eq!(
909 extra.get("email").unwrap().as_str().unwrap(),
910 "test@example.com"
911 );
912 assert_eq!(extra.get("name").unwrap().as_str().unwrap(), "Test User");
913 assert!(extra.get("picture").is_some());
914 }
915
916 #[tokio::test]
917 async fn test_with_allowed_algorithms_rejects_restricted_allowlist() {
918 let server = OAuthTestServer::start().await;
919 let client = server
920 .register_client(json!({ "scope": "openid", "redirect_uris": ["http://localhost"] }))
921 .await;
922
923 let verifier = token_verifier(
924 vec![VerificationStrategies::JWKs {
925 jwks_uri: server.endpoints.jwks.clone(),
926 }],
927 &server.endpoints,
928 None,
929 )
930 .await
931 .with_allowed_algorithms(vec![Algorithm::ES256]);
932
933 let token = server.generate_jwt(&client, server.jwt_options().user_id("hal").build());
934
935 let err = verifier.verify_token(token).await.unwrap_err();
936 assert!(matches!(
937 err,
938 AuthenticationError::TokenVerificationFailed { .. }
939 ));
940 }
941}