rust_mcp_sdk/auth/
auth_info.rs

1#[cfg(feature = "auth")]
2use crate::auth::{AuthClaims, AuthenticationError, IntrospectionResponse};
3use crate::{auth::Audience, utils::unix_timestamp_to_systemtime};
4#[cfg(feature = "auth")]
5use jsonwebtoken::TokenData;
6use serde::{Deserialize, Serialize};
7use serde_json::Map;
8use std::time::SystemTime;
9
10/// Information about a validated access token, provided to request handlers.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AuthInfo {
13    /// Contains a unique id for jwt
14    /// use jti claim if available, otherwise use token or a reliable hash of token
15    pub token_unique_id: String,
16
17    /// The client ID associated with this token.
18    #[serde(skip_serializing_if = "std::option::Option::is_none")]
19    pub client_id: Option<String>,
20
21    /// Optional user identifier for the token
22    #[serde(skip_serializing_if = "std::option::Option::is_none")]
23    pub user_id: Option<String>,
24
25    /// Scopes associated with this token.
26    #[serde(skip_serializing_if = "std::option::Option::is_none")]
27    pub scopes: Option<Vec<String>>,
28
29    /// When the token expires (in seconds since epoch).
30    /// This field is optional, as the token may not have an expiration time.
31    #[serde(skip_serializing_if = "std::option::Option::is_none")]
32    pub expires_at: Option<SystemTime>,
33
34    /// The RFC 8707 resource server identifier for which this token is valid.
35    /// If set, this MUST match the MCP server's resource identifier (minus hash fragment).
36    #[serde(skip_serializing_if = "std::option::Option::is_none")]
37    pub audience: Option<Audience>,
38
39    /// Additional data associated with the token.
40    /// This field can be used to attach any extra data to the auth info.
41    #[serde(flatten, skip_serializing_if = "std::option::Option::is_none")]
42    pub extra: Option<Map<String, serde_json::Value>>,
43}
44
45#[cfg(feature = "auth")]
46impl AuthInfo {
47    pub fn from_token_data(
48        token: String,
49        token_data: TokenData<AuthClaims>,
50        extra: Option<Map<String, serde_json::Value>>,
51    ) -> Result<Self, AuthenticationError> {
52        let client_id = token_data.claims.authorized_party.or(token_data
53            .claims
54            .client_id
55            .or(token_data.claims.application_id));
56
57        let scopes = token_data
58            .claims
59            .scope
60            .map(|c| c.split(" ").map(|s| s.to_string()).collect::<Vec<_>>());
61
62        let expires_at = token_data
63            .claims
64            .expiration
65            .map(|v| unix_timestamp_to_systemtime(v as u64));
66
67        let token_unique_id = token_data.claims.jwt_id.unwrap_or(token);
68
69        Ok(AuthInfo {
70            token_unique_id,
71            client_id,
72            scopes,
73            user_id: token_data.claims.subject,
74            expires_at,
75            audience: token_data.claims.audience,
76            extra,
77        })
78    }
79
80    pub fn from_introspection_response(
81        token: String,
82        data: IntrospectionResponse,
83        extra: Option<Map<String, serde_json::Value>>,
84    ) -> Result<Self, AuthenticationError> {
85        let scopes = data
86            .scope
87            .map(|c| c.split(" ").map(|s| s.to_string()).collect::<Vec<_>>());
88
89        let expires_at = data
90            .expiration
91            .map(|v| unix_timestamp_to_systemtime(v as u64));
92
93        let token_unique_id = data.jwt_id.unwrap_or(token);
94
95        Ok(AuthInfo {
96            token_unique_id,
97            client_id: data.client_id,
98            user_id: data.subject,
99            scopes,
100            expires_at,
101            audience: data.audience,
102            extra,
103        })
104    }
105}