1use serde::{Deserialize, Serialize};
40use std::collections::HashMap;
41
42#[derive(Debug, Clone, thiserror::Error)]
44pub enum JwtError {
45 #[error("invalid token format: {message}")]
47 InvalidFormat {
48 message: String,
50 },
51
52 #[error("invalid signature: {message}")]
54 InvalidSignature {
55 message: String,
57 },
58
59 #[error("token expired")]
61 Expired,
62
63 #[error("token not yet valid")]
65 NotYetValid,
66
67 #[error("invalid issuer: expected {expected}, got {actual}")]
69 InvalidIssuer {
70 expected: String,
72 actual: String,
74 },
75
76 #[error("invalid audience: expected {expected}")]
78 InvalidAudience {
79 expected: String,
81 },
82
83 #[error("missing required claim: {claim}")]
85 MissingClaim {
86 claim: String,
88 },
89
90 #[error("insufficient scope: required {required}")]
92 InsufficientScope {
93 required: String,
95 },
96
97 #[error("JWKS fetch failed: {message}")]
99 JwksFetchError {
100 message: String,
102 },
103
104 #[error("no matching key found for kid: {kid}")]
106 NoMatchingKey {
107 kid: String,
109 },
110
111 #[error("unsupported algorithm: {algorithm}")]
113 UnsupportedAlgorithm {
114 algorithm: String,
116 },
117
118 #[error("algorithm not allowed: {algorithm}")]
120 AlgorithmNotAllowed {
121 algorithm: String,
123 },
124}
125
126#[derive(Debug, Clone, Default)]
128pub struct TokenValidation {
129 pub issuer: Option<String>,
131 pub audience: Option<String>,
133 pub required_scopes: Vec<String>,
135 pub validate_exp: bool,
137 pub leeway_seconds: u64,
139 pub required_claims: Vec<String>,
141 pub allowed_algorithms: Vec<String>,
148}
149
150impl TokenValidation {
151 #[must_use]
153 pub fn new() -> Self {
154 Self {
155 issuer: None,
156 audience: None,
157 required_scopes: Vec::new(),
158 validate_exp: true,
159 leeway_seconds: 60, required_claims: Vec::new(),
161 allowed_algorithms: Vec::new(),
162 }
163 }
164
165 #[must_use]
167 pub fn with_issuer(mut self, issuer: impl Into<String>) -> Self {
168 self.issuer = Some(issuer.into());
169 self
170 }
171
172 #[must_use]
174 pub fn with_audience(mut self, audience: impl Into<String>) -> Self {
175 self.audience = Some(audience.into());
176 self
177 }
178
179 #[must_use]
181 pub fn with_required_scope(mut self, scope: impl Into<String>) -> Self {
182 self.required_scopes.push(scope.into());
183 self
184 }
185
186 #[must_use]
188 pub fn with_required_scopes(
189 mut self,
190 scopes: impl IntoIterator<Item = impl Into<String>>,
191 ) -> Self {
192 self.required_scopes = scopes.into_iter().map(Into::into).collect();
193 self
194 }
195
196 #[must_use]
198 pub const fn with_leeway(mut self, seconds: u64) -> Self {
199 self.leeway_seconds = seconds;
200 self
201 }
202
203 #[must_use]
205 pub const fn without_exp_validation(mut self) -> Self {
206 self.validate_exp = false;
207 self
208 }
209
210 #[must_use]
212 pub fn with_required_claim(mut self, claim: impl Into<String>) -> Self {
213 self.required_claims.push(claim.into());
214 self
215 }
216
217 #[must_use]
222 pub fn with_allowed_algorithms(
223 mut self,
224 algorithms: impl IntoIterator<Item = impl Into<String>>,
225 ) -> Self {
226 self.allowed_algorithms = algorithms.into_iter().map(Into::into).collect();
227 self
228 }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct TokenClaims {
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub iss: Option<String>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub sub: Option<String>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub aud: Option<Audience>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub exp: Option<u64>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub iat: Option<u64>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub nbf: Option<u64>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub jti: Option<String>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub scope: Option<String>,
265
266 #[serde(flatten)]
268 pub extra: HashMap<String, serde_json::Value>,
269}
270
271impl TokenClaims {
272 #[must_use]
274 pub fn has_scope(&self, scope: &str) -> bool {
275 self.scope
276 .as_ref()
277 .is_some_and(|s| s.split_whitespace().any(|part| part == scope))
278 }
279
280 #[must_use]
282 pub fn scopes(&self) -> Vec<&str> {
283 self.scope
284 .as_ref()
285 .map(|s| s.split_whitespace().collect())
286 .unwrap_or_default()
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292#[serde(untagged)]
293pub enum Audience {
294 Single(String),
296 Multiple(Vec<String>),
298}
299
300impl Audience {
301 #[must_use]
303 pub fn contains(&self, aud: &str) -> bool {
304 match self {
305 Self::Single(s) => s == aud,
306 Self::Multiple(v) => v.iter().any(|a| a == aud),
307 }
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct JwksSet {
314 pub keys: Vec<Jwk>,
316}
317
318impl JwksSet {
319 #[must_use]
321 pub fn find_key(&self, kid: &str) -> Option<&Jwk> {
322 self.keys.iter().find(|k| k.kid.as_deref() == Some(kid))
323 }
324
325 #[must_use]
327 pub fn first_key(&self) -> Option<&Jwk> {
328 self.keys.first()
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct Jwk {
335 pub kty: String,
337
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub kid: Option<String>,
341
342 #[serde(rename = "use", skip_serializing_if = "Option::is_none")]
344 pub key_use: Option<String>,
345
346 #[serde(skip_serializing_if = "Option::is_none")]
348 pub alg: Option<String>,
349
350 #[serde(skip_serializing_if = "Option::is_none")]
353 pub n: Option<String>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub e: Option<String>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
362 pub crv: Option<String>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub x: Option<String>,
367
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub y: Option<String>,
371}
372
373impl Jwk {
374 #[must_use]
376 pub fn is_rsa(&self) -> bool {
377 self.kty == "RSA"
378 }
379
380 #[must_use]
382 pub fn is_ec(&self) -> bool {
383 self.kty == "EC"
384 }
385
386 #[must_use]
388 pub fn is_signing_key(&self) -> bool {
389 self.key_use.as_deref() == Some("sig") || self.key_use.is_none()
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct JwtHeader {
396 pub alg: String,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub typ: Option<String>,
402
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub kid: Option<String>,
406}
407
408pub fn decode_header(token: &str) -> Result<JwtHeader, JwtError> {
416 use base64::Engine;
417
418 let parts: Vec<&str> = token.split('.').collect();
419 if parts.len() != 3 {
420 return Err(JwtError::InvalidFormat {
421 message: "token must have 3 parts".to_string(),
422 });
423 }
424
425 let header_json = base64::engine::general_purpose::URL_SAFE_NO_PAD
426 .decode(parts[0])
427 .map_err(|e| JwtError::InvalidFormat {
428 message: format!("invalid header encoding: {e}"),
429 })?;
430
431 serde_json::from_slice(&header_json).map_err(|e| JwtError::InvalidFormat {
432 message: format!("invalid header JSON: {e}"),
433 })
434}
435
436pub fn decode_claims_unverified(token: &str) -> Result<TokenClaims, JwtError> {
445 use base64::Engine;
446
447 let parts: Vec<&str> = token.split('.').collect();
448 if parts.len() != 3 {
449 return Err(JwtError::InvalidFormat {
450 message: "token must have 3 parts".to_string(),
451 });
452 }
453
454 let payload_json = base64::engine::general_purpose::URL_SAFE_NO_PAD
455 .decode(parts[1])
456 .map_err(|e| JwtError::InvalidFormat {
457 message: format!("invalid payload encoding: {e}"),
458 })?;
459
460 serde_json::from_slice(&payload_json).map_err(|e| JwtError::InvalidFormat {
461 message: format!("invalid payload JSON: {e}"),
462 })
463}
464
465pub fn validate_claims(claims: &TokenClaims, validation: &TokenValidation) -> Result<(), JwtError> {
473 let now = std::time::SystemTime::now()
474 .duration_since(std::time::UNIX_EPOCH)
475 .map_or(0, |d| d.as_secs());
476
477 if validation.validate_exp {
479 if let Some(exp) = claims.exp {
480 if now > exp.saturating_add(validation.leeway_seconds) {
481 return Err(JwtError::Expired);
482 }
483 }
484 }
485
486 if let Some(nbf) = claims.nbf {
488 if now.saturating_add(validation.leeway_seconds) < nbf {
489 return Err(JwtError::NotYetValid);
490 }
491 }
492
493 if let Some(ref expected_iss) = validation.issuer {
495 match &claims.iss {
496 Some(actual_iss) if actual_iss != expected_iss => {
497 return Err(JwtError::InvalidIssuer {
498 expected: expected_iss.clone(),
499 actual: actual_iss.clone(),
500 });
501 }
502 None => {
503 return Err(JwtError::MissingClaim {
504 claim: "iss".to_string(),
505 });
506 }
507 _ => {}
508 }
509 }
510
511 if let Some(ref expected_aud) = validation.audience {
513 match &claims.aud {
514 Some(aud) if aud.contains(expected_aud) => {}
515 Some(_) => {
516 return Err(JwtError::InvalidAudience {
517 expected: expected_aud.clone(),
518 });
519 }
520 None => {
521 return Err(JwtError::MissingClaim {
522 claim: "aud".to_string(),
523 });
524 }
525 }
526 }
527
528 for required_scope in &validation.required_scopes {
530 if !claims.has_scope(required_scope) {
531 return Err(JwtError::InsufficientScope {
532 required: required_scope.clone(),
533 });
534 }
535 }
536
537 check_required_claims(claims, &validation.required_claims)?;
539
540 Ok(())
541}
542
543fn check_required_claims(claims: &TokenClaims, required: &[String]) -> Result<(), JwtError> {
552 for claim_name in required {
553 let present = claims.extra.contains_key(claim_name)
554 || match claim_name.as_str() {
555 "iss" => claims.iss.is_some(),
556 "sub" => claims.sub.is_some(),
557 "aud" => claims.aud.is_some(),
558 "exp" => claims.exp.is_some(),
559 "iat" => claims.iat.is_some(),
560 "nbf" => claims.nbf.is_some(),
561 "jti" => claims.jti.is_some(),
562 "scope" => claims.scope.is_some(),
563 _ => false,
564 };
565 if !present {
566 return Err(JwtError::MissingClaim {
567 claim: claim_name.clone(),
568 });
569 }
570 }
571 Ok(())
572}
573
574#[cfg(feature = "jwt")]
576#[derive(Debug, Clone, Copy, PartialEq, Eq)]
577pub enum JwtAlgorithm {
578 RS256,
580 RS384,
582 RS512,
584 ES256,
586 ES384,
588}
589
590#[cfg(feature = "jwt")]
591impl JwtAlgorithm {
592 #[must_use]
594 pub fn parse(alg: &str) -> Option<Self> {
595 match alg {
596 "RS256" => Some(Self::RS256),
597 "RS384" => Some(Self::RS384),
598 "RS512" => Some(Self::RS512),
599 "ES256" => Some(Self::ES256),
600 "ES384" => Some(Self::ES384),
601 _ => None,
602 }
603 }
604
605 fn to_jsonwebtoken_algorithm(self) -> jsonwebtoken::Algorithm {
607 match self {
608 Self::RS256 => jsonwebtoken::Algorithm::RS256,
609 Self::RS384 => jsonwebtoken::Algorithm::RS384,
610 Self::RS512 => jsonwebtoken::Algorithm::RS512,
611 Self::ES256 => jsonwebtoken::Algorithm::ES256,
612 Self::ES384 => jsonwebtoken::Algorithm::ES384,
613 }
614 }
615}
616
617#[cfg(feature = "jwt")]
618impl std::fmt::Display for JwtAlgorithm {
619 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 match self {
621 Self::RS256 => write!(f, "RS256"),
622 Self::RS384 => write!(f, "RS384"),
623 Self::RS512 => write!(f, "RS512"),
624 Self::ES256 => write!(f, "ES256"),
625 Self::ES384 => write!(f, "ES384"),
626 }
627 }
628}
629
630#[cfg(feature = "jwt")]
632fn create_decoding_key(
633 jwk: &Jwk,
634 alg: JwtAlgorithm,
635) -> Result<jsonwebtoken::DecodingKey, JwtError> {
636 match alg {
637 JwtAlgorithm::RS256 | JwtAlgorithm::RS384 | JwtAlgorithm::RS512 => {
638 let n = jwk.n.as_ref().ok_or_else(|| JwtError::InvalidFormat {
639 message: "RSA key missing 'n' component".to_string(),
640 })?;
641 let e = jwk.e.as_ref().ok_or_else(|| JwtError::InvalidFormat {
642 message: "RSA key missing 'e' component".to_string(),
643 })?;
644 jsonwebtoken::DecodingKey::from_rsa_components(n, e).map_err(|e| {
645 JwtError::InvalidFormat {
646 message: format!("invalid RSA key: {e}"),
647 }
648 })
649 }
650 JwtAlgorithm::ES256 | JwtAlgorithm::ES384 => {
651 let x = jwk.x.as_ref().ok_or_else(|| JwtError::InvalidFormat {
652 message: "EC key missing 'x' component".to_string(),
653 })?;
654 let y = jwk.y.as_ref().ok_or_else(|| JwtError::InvalidFormat {
655 message: "EC key missing 'y' component".to_string(),
656 })?;
657 jsonwebtoken::DecodingKey::from_ec_components(x, y).map_err(|e| {
658 JwtError::InvalidFormat {
659 message: format!("invalid EC key: {e}"),
660 }
661 })
662 }
663 }
664}
665
666#[cfg(feature = "jwt")]
668fn build_jwt_validation(
669 validation: &TokenValidation,
670 alg: JwtAlgorithm,
671) -> jsonwebtoken::Validation {
672 let mut jwt_validation = jsonwebtoken::Validation::new(alg.to_jsonwebtoken_algorithm());
673
674 jwt_validation.leeway = validation.leeway_seconds;
676
677 if let Some(ref iss) = validation.issuer {
679 jwt_validation.set_issuer(&[iss]);
680 }
681
682 if let Some(ref aud) = validation.audience {
684 jwt_validation.set_audience(&[aud]);
685 }
686
687 jwt_validation.validate_exp = validation.validate_exp;
689
690 jwt_validation
697}
698
699#[cfg(feature = "jwt")]
746pub fn validate_token(
747 token: &str,
748 jwks: &JwksSet,
749 validation: &TokenValidation,
750) -> Result<TokenClaims, JwtError> {
751 let header = decode_header(token)?;
753
754 if !validation.allowed_algorithms.is_empty()
758 && !validation
759 .allowed_algorithms
760 .iter()
761 .any(|a| a == &header.alg)
762 {
763 return Err(JwtError::AlgorithmNotAllowed {
764 algorithm: header.alg,
765 });
766 }
767
768 let alg = JwtAlgorithm::parse(&header.alg).ok_or_else(|| JwtError::UnsupportedAlgorithm {
770 algorithm: header.alg.clone(),
771 })?;
772
773 let jwk = if let Some(ref kid) = header.kid {
775 jwks.find_key(kid)
777 .ok_or_else(|| JwtError::NoMatchingKey { kid: kid.clone() })?
778 } else {
779 jwks.keys
781 .iter()
782 .find(|k| k.is_signing_key() && k.alg.as_deref() == Some(&header.alg))
783 .or_else(|| jwks.first_key())
784 .ok_or_else(|| JwtError::NoMatchingKey {
785 kid: "<no kid specified>".to_string(),
786 })?
787 };
788
789 let decoding_key = create_decoding_key(jwk, alg)?;
791
792 let jwt_validation = build_jwt_validation(validation, alg);
794
795 let token_data = jsonwebtoken::decode::<TokenClaims>(token, &decoding_key, &jwt_validation)
797 .map_err(|e| match e.kind() {
798 jsonwebtoken::errors::ErrorKind::ExpiredSignature => JwtError::Expired,
799 jsonwebtoken::errors::ErrorKind::ImmatureSignature => JwtError::NotYetValid,
800 jsonwebtoken::errors::ErrorKind::InvalidSignature => JwtError::InvalidSignature {
801 message: "signature verification failed".to_string(),
802 },
803 jsonwebtoken::errors::ErrorKind::InvalidAudience => JwtError::InvalidAudience {
804 expected: validation.audience.clone().unwrap_or_default(),
805 },
806 jsonwebtoken::errors::ErrorKind::InvalidIssuer => JwtError::InvalidIssuer {
807 expected: validation.issuer.clone().unwrap_or_default(),
808 actual: "<unknown>".to_string(),
809 },
810 _ => JwtError::InvalidFormat {
811 message: format!("token validation failed: {e}"),
812 },
813 })?;
814
815 let claims = token_data.claims;
818 for required_scope in &validation.required_scopes {
819 if !claims.has_scope(required_scope) {
820 return Err(JwtError::InsufficientScope {
821 required: required_scope.clone(),
822 });
823 }
824 }
825 check_required_claims(&claims, &validation.required_claims)?;
826
827 Ok(claims)
828}
829
830#[cfg(feature = "jwt")]
858pub async fn fetch_jwks(jwks_uri: &str) -> Result<JwksSet, JwtError> {
859 if !jwks_uri.starts_with("https://") {
862 return Err(JwtError::JwksFetchError {
863 message: format!("JWKS URI must use https://, got: {jwks_uri}"),
864 });
865 }
866
867 let response = reqwest::get(jwks_uri)
868 .await
869 .map_err(|e| JwtError::JwksFetchError {
870 message: format!("HTTP request failed: {e}"),
871 })?;
872
873 if !response.status().is_success() {
874 return Err(JwtError::JwksFetchError {
875 message: format!("HTTP {} from JWKS endpoint", response.status()),
876 });
877 }
878
879 response.json().await.map_err(|e| JwtError::JwksFetchError {
880 message: format!("Failed to parse JWKS response: {e}"),
881 })
882}
883
884#[cfg(feature = "jwt")]
933pub async fn validate_token_with_fetch(
934 token: &str,
935 jwks_uri: &str,
936 validation: &TokenValidation,
937) -> Result<TokenClaims, JwtError> {
938 let jwks = fetch_jwks(jwks_uri).await?;
940
941 validate_token(token, &jwks, validation)
943}
944
945#[cfg(test)]
946mod tests {
947 use super::*;
948
949 #[test]
950 fn test_token_validation_builder() {
951 let validation = TokenValidation::new()
952 .with_issuer("https://auth.example.com")
953 .with_audience("https://mcp.example.com")
954 .with_required_scope("mcp:read")
955 .with_leeway(120);
956
957 assert_eq!(
958 validation.issuer,
959 Some("https://auth.example.com".to_string())
960 );
961 assert_eq!(
962 validation.audience,
963 Some("https://mcp.example.com".to_string())
964 );
965 assert_eq!(validation.required_scopes, vec!["mcp:read"]);
966 assert_eq!(validation.leeway_seconds, 120);
967 }
968
969 #[test]
970 fn test_audience_contains() {
971 let single = Audience::Single("api".to_string());
972 assert!(single.contains("api"));
973 assert!(!single.contains("other"));
974
975 let multiple = Audience::Multiple(vec!["api".to_string(), "web".to_string()]);
976 assert!(multiple.contains("api"));
977 assert!(multiple.contains("web"));
978 assert!(!multiple.contains("other"));
979 }
980
981 #[test]
982 fn test_token_claims_scopes() {
983 let claims = TokenClaims {
984 iss: None,
985 sub: None,
986 aud: None,
987 exp: None,
988 iat: None,
989 nbf: None,
990 jti: None,
991 scope: Some("mcp:read mcp:write".to_string()),
992 extra: HashMap::new(),
993 };
994
995 assert!(claims.has_scope("mcp:read"));
996 assert!(claims.has_scope("mcp:write"));
997 assert!(!claims.has_scope("mcp:admin"));
998
999 let scopes = claims.scopes();
1000 assert_eq!(scopes, vec!["mcp:read", "mcp:write"]);
1001 }
1002
1003 #[test]
1004 fn test_decode_header() {
1005 let token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0xIn0.e30.sig";
1007 let header = decode_header(token).unwrap();
1008
1009 assert_eq!(header.alg, "RS256");
1010 assert_eq!(header.typ, Some("JWT".to_string()));
1011 assert_eq!(header.kid, Some("key-1".to_string()));
1012 }
1013
1014 #[test]
1015 fn test_decode_header_invalid() {
1016 assert!(decode_header("invalid").is_err());
1017 assert!(decode_header("a.b").is_err());
1018 assert!(decode_header("").is_err());
1019 }
1020
1021 #[test]
1022 fn test_decode_claims_unverified() {
1023 let token = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0ZXN0Iiwic3ViIjoidXNlcjEyMyIsImV4cCI6OTk5OTk5OTk5OX0.sig";
1025 let claims = decode_claims_unverified(token).unwrap();
1026
1027 assert_eq!(claims.iss, Some("test".to_string()));
1028 assert_eq!(claims.sub, Some("user123".to_string()));
1029 assert_eq!(claims.exp, Some(9_999_999_999));
1030 }
1031
1032 #[test]
1033 fn test_validate_claims_expired() {
1034 let claims = TokenClaims {
1035 iss: None,
1036 sub: None,
1037 aud: None,
1038 exp: Some(1000), iat: None,
1040 nbf: None,
1041 jti: None,
1042 scope: None,
1043 extra: HashMap::new(),
1044 };
1045
1046 let validation = TokenValidation::new();
1047 let result = validate_claims(&claims, &validation);
1048 assert!(matches!(result, Err(JwtError::Expired)));
1049 }
1050
1051 #[test]
1052 fn test_validate_claims_wrong_issuer() {
1053 let claims = TokenClaims {
1054 iss: Some("wrong-issuer".to_string()),
1055 sub: None,
1056 aud: None,
1057 exp: Some(u64::MAX),
1058 iat: None,
1059 nbf: None,
1060 jti: None,
1061 scope: None,
1062 extra: HashMap::new(),
1063 };
1064
1065 let validation = TokenValidation::new().with_issuer("expected-issuer");
1066 let result = validate_claims(&claims, &validation);
1067 assert!(matches!(result, Err(JwtError::InvalidIssuer { .. })));
1068 }
1069
1070 #[test]
1071 fn test_validate_claims_insufficient_scope() {
1072 let claims = TokenClaims {
1073 iss: None,
1074 sub: None,
1075 aud: None,
1076 exp: Some(u64::MAX),
1077 iat: None,
1078 nbf: None,
1079 jti: None,
1080 scope: Some("mcp:read".to_string()),
1081 extra: HashMap::new(),
1082 };
1083
1084 let validation = TokenValidation::new()
1085 .without_exp_validation()
1086 .with_required_scope("mcp:admin");
1087 let result = validate_claims(&claims, &validation);
1088 assert!(matches!(result, Err(JwtError::InsufficientScope { .. })));
1089 }
1090
1091 #[test]
1092 fn test_validate_claims_success() {
1093 let claims = TokenClaims {
1094 iss: Some("https://auth.example.com".to_string()),
1095 sub: Some("user123".to_string()),
1096 aud: Some(Audience::Single("https://mcp.example.com".to_string())),
1097 exp: Some(u64::MAX),
1098 iat: Some(1000),
1099 nbf: None,
1100 jti: None,
1101 scope: Some("mcp:read mcp:write".to_string()),
1102 extra: HashMap::new(),
1103 };
1104
1105 let validation = TokenValidation::new()
1106 .with_issuer("https://auth.example.com")
1107 .with_audience("https://mcp.example.com")
1108 .with_required_scope("mcp:read");
1109
1110 let result = validate_claims(&claims, &validation);
1111 assert!(result.is_ok());
1112 }
1113
1114 #[test]
1115 fn test_jwk_type_detection() {
1116 let rsa_key = Jwk {
1117 kty: "RSA".to_string(),
1118 kid: Some("rsa-key".to_string()),
1119 key_use: Some("sig".to_string()),
1120 alg: Some("RS256".to_string()),
1121 n: Some("...".to_string()),
1122 e: Some("AQAB".to_string()),
1123 crv: None,
1124 x: None,
1125 y: None,
1126 };
1127
1128 assert!(rsa_key.is_rsa());
1129 assert!(!rsa_key.is_ec());
1130 assert!(rsa_key.is_signing_key());
1131
1132 let ec_key = Jwk {
1133 kty: "EC".to_string(),
1134 kid: Some("ec-key".to_string()),
1135 key_use: Some("sig".to_string()),
1136 alg: Some("ES256".to_string()),
1137 n: None,
1138 e: None,
1139 crv: Some("P-256".to_string()),
1140 x: Some("...".to_string()),
1141 y: Some("...".to_string()),
1142 };
1143
1144 assert!(!ec_key.is_rsa());
1145 assert!(ec_key.is_ec());
1146 assert!(ec_key.is_signing_key());
1147 }
1148
1149 #[test]
1150 fn test_jwks_find_key() {
1151 let jwks = JwksSet {
1152 keys: vec![
1153 Jwk {
1154 kty: "RSA".to_string(),
1155 kid: Some("key-1".to_string()),
1156 key_use: Some("sig".to_string()),
1157 alg: Some("RS256".to_string()),
1158 n: Some("...".to_string()),
1159 e: Some("AQAB".to_string()),
1160 crv: None,
1161 x: None,
1162 y: None,
1163 },
1164 Jwk {
1165 kty: "RSA".to_string(),
1166 kid: Some("key-2".to_string()),
1167 key_use: Some("sig".to_string()),
1168 alg: Some("RS256".to_string()),
1169 n: Some("...".to_string()),
1170 e: Some("AQAB".to_string()),
1171 crv: None,
1172 x: None,
1173 y: None,
1174 },
1175 ],
1176 };
1177
1178 let key = jwks.find_key("key-1");
1179 assert!(key.is_some());
1180 assert_eq!(key.unwrap().kid, Some("key-1".to_string()));
1181
1182 let key = jwks.find_key("key-2");
1183 assert!(key.is_some());
1184
1185 let key = jwks.find_key("nonexistent");
1186 assert!(key.is_none());
1187
1188 let first = jwks.first_key();
1189 assert!(first.is_some());
1190 }
1191
1192 #[test]
1193 fn test_jwt_error_display() {
1194 let err = JwtError::Expired;
1195 assert_eq!(err.to_string(), "token expired");
1196
1197 let err = JwtError::InvalidIssuer {
1198 expected: "a".to_string(),
1199 actual: "b".to_string(),
1200 };
1201 assert!(err.to_string().contains("expected a"));
1202 assert!(err.to_string().contains("got b"));
1203 }
1204}
1205
1206#[cfg(all(test, feature = "jwt"))]
1208mod signature_tests {
1209 use super::*;
1210 use rsa::pkcs8::EncodePrivateKey;
1211 use rsa::traits::PublicKeyParts;
1212
1213 fn create_test_jwt(
1215 alg: jsonwebtoken::Algorithm,
1216 encoding_key: &jsonwebtoken::EncodingKey,
1217 kid: Option<&str>,
1218 claims: &TokenClaims,
1219 ) -> String {
1220 let mut header = jsonwebtoken::Header::new(alg);
1221 header.kid = kid.map(String::from);
1222 jsonwebtoken::encode(&header, claims, encoding_key).expect("failed to encode JWT")
1223 }
1224
1225 fn make_test_claims() -> TokenClaims {
1227 TokenClaims {
1228 iss: Some("https://auth.example.com".to_string()),
1229 sub: Some("user123".to_string()),
1230 aud: Some(Audience::Single("https://mcp.example.com".to_string())),
1231 exp: Some(u64::MAX / 2), iat: Some(1_000_000),
1233 nbf: None,
1234 jti: Some("test-jti-123".to_string()),
1235 scope: Some("mcp:read mcp:write".to_string()),
1236 extra: HashMap::new(),
1237 }
1238 }
1239
1240 #[test]
1241 fn test_jwt_algorithm_parse() {
1242 assert_eq!(JwtAlgorithm::parse("RS256"), Some(JwtAlgorithm::RS256));
1243 assert_eq!(JwtAlgorithm::parse("RS384"), Some(JwtAlgorithm::RS384));
1244 assert_eq!(JwtAlgorithm::parse("RS512"), Some(JwtAlgorithm::RS512));
1245 assert_eq!(JwtAlgorithm::parse("ES256"), Some(JwtAlgorithm::ES256));
1246 assert_eq!(JwtAlgorithm::parse("ES384"), Some(JwtAlgorithm::ES384));
1247 assert_eq!(JwtAlgorithm::parse("HS256"), None);
1248 assert_eq!(JwtAlgorithm::parse("invalid"), None);
1249 }
1250
1251 #[test]
1252 fn test_jwt_algorithm_display() {
1253 assert_eq!(JwtAlgorithm::RS256.to_string(), "RS256");
1254 assert_eq!(JwtAlgorithm::ES256.to_string(), "ES256");
1255 assert_eq!(JwtAlgorithm::RS512.to_string(), "RS512");
1256 }
1257
1258 #[test]
1259 fn test_validate_token_rs256() {
1260 use base64::Engine;
1262 use rand::rngs::OsRng;
1263 use rsa::RsaPrivateKey;
1264
1265 let mut rng = OsRng;
1266 let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
1267 let public_key = private_key.to_public_key();
1268
1269 let n_bytes = public_key.n().to_bytes_be();
1271 let e_bytes = public_key.e().to_bytes_be();
1272 let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes);
1273 let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes);
1274
1275 let jwks = JwksSet {
1277 keys: vec![Jwk {
1278 kty: "RSA".to_string(),
1279 kid: Some("test-key-1".to_string()),
1280 key_use: Some("sig".to_string()),
1281 alg: Some("RS256".to_string()),
1282 n: Some(n),
1283 e: Some(e),
1284 crv: None,
1285 x: None,
1286 y: None,
1287 }],
1288 };
1289
1290 let pem = private_key
1292 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1293 .expect("failed to encode private key");
1294 let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes())
1295 .expect("failed to create encoding key");
1296
1297 let claims = make_test_claims();
1299 let token = create_test_jwt(
1300 jsonwebtoken::Algorithm::RS256,
1301 &encoding_key,
1302 Some("test-key-1"),
1303 &claims,
1304 );
1305
1306 let validation = TokenValidation::new()
1308 .with_issuer("https://auth.example.com")
1309 .with_audience("https://mcp.example.com");
1310
1311 let result = validate_token(&token, &jwks, &validation);
1312 assert!(result.is_ok(), "validation failed: {:?}", result.err());
1313
1314 let validated_claims = result.unwrap();
1315 assert_eq!(validated_claims.sub, Some("user123".to_string()));
1316 assert_eq!(
1317 validated_claims.iss,
1318 Some("https://auth.example.com".to_string())
1319 );
1320 }
1321
1322 #[test]
1323 fn test_validate_token_algorithm_allowlist() {
1324 use base64::Engine;
1325 use rand::rngs::OsRng;
1326 use rsa::RsaPrivateKey;
1327
1328 let mut rng = OsRng;
1329 let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
1330 let public_key = private_key.to_public_key();
1331 let n =
1332 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key.n().to_bytes_be());
1333 let e =
1334 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key.e().to_bytes_be());
1335
1336 let jwks = JwksSet {
1337 keys: vec![Jwk {
1338 kty: "RSA".to_string(),
1339 kid: Some("test-key-1".to_string()),
1340 key_use: Some("sig".to_string()),
1341 alg: Some("RS256".to_string()),
1342 n: Some(n),
1343 e: Some(e),
1344 crv: None,
1345 x: None,
1346 y: None,
1347 }],
1348 };
1349
1350 let pem = private_key
1351 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1352 .expect("failed to encode private key");
1353 let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes())
1354 .expect("failed to create encoding key");
1355 let token = create_test_jwt(
1356 jsonwebtoken::Algorithm::RS256,
1357 &encoding_key,
1358 Some("test-key-1"),
1359 &make_test_claims(),
1360 );
1361
1362 let allowed = TokenValidation::new()
1364 .with_issuer("https://auth.example.com")
1365 .with_audience("https://mcp.example.com")
1366 .with_allowed_algorithms(["RS256"]);
1367 assert!(validate_token(&token, &jwks, &allowed).is_ok());
1368
1369 let disallowed = TokenValidation::new()
1371 .with_issuer("https://auth.example.com")
1372 .with_audience("https://mcp.example.com")
1373 .with_allowed_algorithms(["ES256"]);
1374 assert!(matches!(
1375 validate_token(&token, &jwks, &disallowed),
1376 Err(JwtError::AlgorithmNotAllowed { .. })
1377 ));
1378 }
1379
1380 #[tokio::test]
1381 async fn test_fetch_jwks_rejects_non_https() {
1382 let err = fetch_jwks("http://auth.example.com/.well-known/jwks.json")
1383 .await
1384 .expect_err("non-https JWKS URI must be rejected");
1385 assert!(matches!(err, JwtError::JwksFetchError { .. }));
1386 }
1387
1388 #[test]
1389 fn test_validate_token_es256() {
1390 use base64::Engine;
1391 use p256::ecdsa::{SigningKey, VerifyingKey};
1392 use p256::elliptic_curve::sec1::ToEncodedPoint;
1393 use p256::pkcs8::EncodePrivateKey as EcEncodePrivateKey;
1394 use rand::rngs::OsRng;
1395
1396 let signing_key = SigningKey::random(&mut OsRng);
1398 let verifying_key = VerifyingKey::from(&signing_key);
1399
1400 let point = verifying_key.as_affine().to_encoded_point(false);
1402 let x_bytes = point.x().expect("x coordinate");
1403 let y_bytes = point.y().expect("y coordinate");
1404 let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(x_bytes);
1405 let y = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(y_bytes);
1406
1407 let jwks = JwksSet {
1409 keys: vec![Jwk {
1410 kty: "EC".to_string(),
1411 kid: Some("test-ec-key-1".to_string()),
1412 key_use: Some("sig".to_string()),
1413 alg: Some("ES256".to_string()),
1414 n: None,
1415 e: None,
1416 crv: Some("P-256".to_string()),
1417 x: Some(x),
1418 y: Some(y),
1419 }],
1420 };
1421
1422 let pkcs8_der = signing_key.to_pkcs8_der().expect("failed to encode EC key");
1424 let encoding_key = jsonwebtoken::EncodingKey::from_ec_der(pkcs8_der.as_bytes());
1425
1426 let claims = make_test_claims();
1428 let token = create_test_jwt(
1429 jsonwebtoken::Algorithm::ES256,
1430 &encoding_key,
1431 Some("test-ec-key-1"),
1432 &claims,
1433 );
1434
1435 let validation = TokenValidation::new()
1437 .with_issuer("https://auth.example.com")
1438 .with_audience("https://mcp.example.com");
1439
1440 let result = validate_token(&token, &jwks, &validation);
1441 assert!(
1442 result.is_ok(),
1443 "ES256 validation failed: {:?}",
1444 result.err()
1445 );
1446
1447 let validated_claims = result.unwrap();
1448 assert_eq!(validated_claims.sub, Some("user123".to_string()));
1449 }
1450
1451 #[test]
1456 fn test_validate_token_enforces_custom_required_claims() {
1457 use base64::Engine;
1458 use p256::ecdsa::{SigningKey, VerifyingKey};
1459 use p256::elliptic_curve::sec1::ToEncodedPoint;
1460 use p256::pkcs8::EncodePrivateKey as EcEncodePrivateKey;
1461 use rand::rngs::OsRng;
1462
1463 let signing_key = SigningKey::random(&mut OsRng);
1464 let verifying_key = VerifyingKey::from(&signing_key);
1465 let point = verifying_key.as_affine().to_encoded_point(false);
1466 let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(point.x().expect("x"));
1467 let y = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(point.y().expect("y"));
1468
1469 let jwks = JwksSet {
1470 keys: vec![Jwk {
1471 kty: "EC".to_string(),
1472 kid: Some("ec-1".to_string()),
1473 key_use: Some("sig".to_string()),
1474 alg: Some("ES256".to_string()),
1475 n: None,
1476 e: None,
1477 crv: Some("P-256".to_string()),
1478 x: Some(x),
1479 y: Some(y),
1480 }],
1481 };
1482 let pkcs8 = signing_key.to_pkcs8_der().expect("encode EC key");
1483 let encoding_key = jsonwebtoken::EncodingKey::from_ec_der(pkcs8.as_bytes());
1484
1485 let validation = TokenValidation::new()
1486 .with_issuer("https://auth.example.com")
1487 .with_audience("https://mcp.example.com")
1488 .with_required_claim("tenant_id");
1489
1490 let token = create_test_jwt(
1493 jsonwebtoken::Algorithm::ES256,
1494 &encoding_key,
1495 Some("ec-1"),
1496 &make_test_claims(),
1497 );
1498 match validate_token(&token, &jwks, &validation) {
1499 Err(JwtError::MissingClaim { claim }) => assert_eq!(claim.as_str(), "tenant_id"),
1500 other => panic!("expected MissingClaim(tenant_id), got {other:?}"),
1501 }
1502
1503 let mut claims = make_test_claims();
1505 claims
1506 .extra
1507 .insert("tenant_id".to_string(), serde_json::json!("acme"));
1508 let token = create_test_jwt(
1509 jsonwebtoken::Algorithm::ES256,
1510 &encoding_key,
1511 Some("ec-1"),
1512 &claims,
1513 );
1514 assert!(
1515 validate_token(&token, &jwks, &validation).is_ok(),
1516 "token with the required custom claim should validate"
1517 );
1518
1519 let mut claims = make_test_claims();
1523 claims
1524 .extra
1525 .insert("tenant_id".to_string(), serde_json::json!("acme"));
1526 claims.exp = None;
1527 let token = create_test_jwt(
1528 jsonwebtoken::Algorithm::ES256,
1529 &encoding_key,
1530 Some("ec-1"),
1531 &claims,
1532 );
1533 assert!(
1534 validate_token(&token, &jwks, &validation).is_err(),
1535 "missing exp must still be rejected when required_claims is set"
1536 );
1537 }
1538
1539 #[test]
1540 fn test_validate_token_invalid_signature() {
1541 use base64::Engine;
1542 use rand::rngs::OsRng;
1543 use rsa::RsaPrivateKey;
1544
1545 let mut rng = OsRng;
1546
1547 let private_key_1 = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key 1");
1549 let private_key_2 = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key 2");
1550 let public_key_2 = private_key_2.to_public_key();
1551
1552 let n_bytes = public_key_2.n().to_bytes_be();
1554 let e_bytes = public_key_2.e().to_bytes_be();
1555 let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes);
1556 let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes);
1557
1558 let jwks = JwksSet {
1559 keys: vec![Jwk {
1560 kty: "RSA".to_string(),
1561 kid: Some("key-2".to_string()),
1562 key_use: Some("sig".to_string()),
1563 alg: Some("RS256".to_string()),
1564 n: Some(n),
1565 e: Some(e),
1566 crv: None,
1567 x: None,
1568 y: None,
1569 }],
1570 };
1571
1572 let pem_1 = private_key_1
1574 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1575 .expect("failed to encode private key");
1576 let encoding_key_1 = jsonwebtoken::EncodingKey::from_rsa_pem(pem_1.as_bytes())
1577 .expect("failed to create encoding key");
1578
1579 let claims = make_test_claims();
1580 let token = create_test_jwt(
1581 jsonwebtoken::Algorithm::RS256,
1582 &encoding_key_1,
1583 Some("key-2"), &claims,
1585 );
1586
1587 let validation = TokenValidation::new()
1589 .with_issuer("https://auth.example.com")
1590 .with_audience("https://mcp.example.com");
1591
1592 let result = validate_token(&token, &jwks, &validation);
1593 assert!(result.is_err());
1594 assert!(matches!(
1595 result.unwrap_err(),
1596 JwtError::InvalidSignature { .. }
1597 ));
1598 }
1599
1600 #[test]
1601 fn test_validate_token_no_matching_key() {
1602 let jwks = JwksSet {
1604 keys: vec![Jwk {
1605 kty: "RSA".to_string(),
1606 kid: Some("different-key".to_string()),
1607 key_use: Some("sig".to_string()),
1608 alg: Some("RS256".to_string()),
1609 n: Some("test".to_string()),
1610 e: Some("AQAB".to_string()),
1611 crv: None,
1612 x: None,
1613 y: None,
1614 }],
1615 };
1616
1617 let token = "eyJhbGciOiJSUzI1NiIsImtpZCI6Im5vbmV4aXN0ZW50LWtleSJ9.eyJpc3MiOiJ0ZXN0In0.sig";
1620
1621 let validation = TokenValidation::new();
1622 let result = validate_token(token, &jwks, &validation);
1623
1624 assert!(result.is_err());
1625 assert!(matches!(
1626 result.unwrap_err(),
1627 JwtError::NoMatchingKey { .. }
1628 ));
1629 }
1630
1631 #[test]
1632 fn test_validate_token_unsupported_algorithm() {
1633 let jwks = JwksSet { keys: vec![] };
1634
1635 let token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0In0.sig";
1638
1639 let validation = TokenValidation::new();
1640 let result = validate_token(token, &jwks, &validation);
1641
1642 assert!(result.is_err());
1643 assert!(matches!(
1644 result.unwrap_err(),
1645 JwtError::UnsupportedAlgorithm { .. }
1646 ));
1647 }
1648
1649 #[test]
1650 fn test_validate_token_scope_validation() {
1651 use base64::Engine;
1652 use rand::rngs::OsRng;
1653 use rsa::RsaPrivateKey;
1654
1655 let mut rng = OsRng;
1656 let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
1657 let public_key = private_key.to_public_key();
1658
1659 let n_bytes = public_key.n().to_bytes_be();
1660 let e_bytes = public_key.e().to_bytes_be();
1661 let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes);
1662 let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes);
1663
1664 let jwks = JwksSet {
1665 keys: vec![Jwk {
1666 kty: "RSA".to_string(),
1667 kid: Some("test-key".to_string()),
1668 key_use: Some("sig".to_string()),
1669 alg: Some("RS256".to_string()),
1670 n: Some(n),
1671 e: Some(e),
1672 crv: None,
1673 x: None,
1674 y: None,
1675 }],
1676 };
1677
1678 let pem = private_key
1679 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1680 .expect("failed to encode private key");
1681 let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes())
1682 .expect("failed to create encoding key");
1683
1684 let mut claims = make_test_claims();
1686 claims.scope = Some("mcp:read".to_string()); let token = create_test_jwt(
1689 jsonwebtoken::Algorithm::RS256,
1690 &encoding_key,
1691 Some("test-key"),
1692 &claims,
1693 );
1694
1695 let validation = TokenValidation::new()
1697 .with_issuer("https://auth.example.com")
1698 .with_audience("https://mcp.example.com")
1699 .with_required_scope("mcp:admin"); let result = validate_token(&token, &jwks, &validation);
1702 assert!(result.is_err());
1703 assert!(matches!(
1704 result.unwrap_err(),
1705 JwtError::InsufficientScope { .. }
1706 ));
1707 }
1708
1709 #[test]
1710 fn test_validate_token_expired() {
1711 use base64::Engine;
1712 use rand::rngs::OsRng;
1713 use rsa::RsaPrivateKey;
1714
1715 let mut rng = OsRng;
1716 let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
1717 let public_key = private_key.to_public_key();
1718
1719 let n_bytes = public_key.n().to_bytes_be();
1720 let e_bytes = public_key.e().to_bytes_be();
1721 let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes);
1722 let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes);
1723
1724 let jwks = JwksSet {
1725 keys: vec![Jwk {
1726 kty: "RSA".to_string(),
1727 kid: Some("test-key".to_string()),
1728 key_use: Some("sig".to_string()),
1729 alg: Some("RS256".to_string()),
1730 n: Some(n),
1731 e: Some(e),
1732 crv: None,
1733 x: None,
1734 y: None,
1735 }],
1736 };
1737
1738 let pem = private_key
1739 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1740 .expect("failed to encode private key");
1741 let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes())
1742 .expect("failed to create encoding key");
1743
1744 let mut claims = make_test_claims();
1746 claims.exp = Some(1000); let token = create_test_jwt(
1749 jsonwebtoken::Algorithm::RS256,
1750 &encoding_key,
1751 Some("test-key"),
1752 &claims,
1753 );
1754
1755 let validation = TokenValidation::new()
1756 .with_issuer("https://auth.example.com")
1757 .with_audience("https://mcp.example.com");
1758
1759 let result = validate_token(&token, &jwks, &validation);
1760 assert!(result.is_err());
1761 assert!(matches!(result.unwrap_err(), JwtError::Expired));
1762 }
1763
1764 #[test]
1765 fn test_validate_token_key_selection_by_algorithm() {
1766 use base64::Engine;
1767 use rand::rngs::OsRng;
1768 use rsa::RsaPrivateKey;
1769
1770 let mut rng = OsRng;
1771 let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
1772 let public_key = private_key.to_public_key();
1773
1774 let n_bytes = public_key.n().to_bytes_be();
1775 let e_bytes = public_key.e().to_bytes_be();
1776 let n = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes);
1777 let e = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes);
1778
1779 let jwks = JwksSet {
1781 keys: vec![Jwk {
1782 kty: "RSA".to_string(),
1783 kid: None, key_use: Some("sig".to_string()),
1785 alg: Some("RS256".to_string()),
1786 n: Some(n),
1787 e: Some(e),
1788 crv: None,
1789 x: None,
1790 y: None,
1791 }],
1792 };
1793
1794 let pem = private_key
1795 .to_pkcs8_pem(rsa::pkcs8::LineEnding::LF)
1796 .expect("failed to encode private key");
1797 let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes())
1798 .expect("failed to create encoding key");
1799
1800 let claims = make_test_claims();
1802 let token = create_test_jwt(
1803 jsonwebtoken::Algorithm::RS256,
1804 &encoding_key,
1805 None, &claims,
1807 );
1808
1809 let validation = TokenValidation::new()
1810 .with_issuer("https://auth.example.com")
1811 .with_audience("https://mcp.example.com");
1812
1813 let result = validate_token(&token, &jwks, &validation);
1814 assert!(
1815 result.is_ok(),
1816 "key selection by algorithm failed: {:?}",
1817 result.err()
1818 );
1819 }
1820
1821 #[test]
1822 fn test_create_decoding_key_missing_rsa_components() {
1823 let incomplete_jwk = Jwk {
1824 kty: "RSA".to_string(),
1825 kid: Some("incomplete".to_string()),
1826 key_use: Some("sig".to_string()),
1827 alg: Some("RS256".to_string()),
1828 n: None, e: Some("AQAB".to_string()),
1830 crv: None,
1831 x: None,
1832 y: None,
1833 };
1834
1835 let result = create_decoding_key(&incomplete_jwk, JwtAlgorithm::RS256);
1836 assert!(result.is_err());
1837 assert!(matches!(
1838 result.unwrap_err(),
1839 JwtError::InvalidFormat { .. }
1840 ));
1841 }
1842
1843 #[test]
1844 fn test_create_decoding_key_missing_ec_components() {
1845 let incomplete_jwk = Jwk {
1846 kty: "EC".to_string(),
1847 kid: Some("incomplete-ec".to_string()),
1848 key_use: Some("sig".to_string()),
1849 alg: Some("ES256".to_string()),
1850 n: None,
1851 e: None,
1852 crv: Some("P-256".to_string()),
1853 x: Some("test".to_string()),
1854 y: None, };
1856
1857 let result = create_decoding_key(&incomplete_jwk, JwtAlgorithm::ES256);
1858 assert!(result.is_err());
1859 assert!(matches!(
1860 result.unwrap_err(),
1861 JwtError::InvalidFormat { .. }
1862 ));
1863 }
1864}