rust_mcp_sdk/auth/
auth_provider.rs

1mod remote_auth_provider;
2use crate::auth::OauthEndpoint;
3use crate::auth::{AuthInfo, AuthenticationError};
4use crate::mcp_http::{GenericBody, GenericBodyExt, McpAppState};
5use crate::mcp_server::error::TransportServerError;
6use async_trait::async_trait;
7use http::Method;
8pub use remote_auth_provider::*;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12#[async_trait]
13pub trait AuthProvider: Send + Sync {
14    async fn verify_token(&self, access_token: String) -> Result<AuthInfo, AuthenticationError>;
15
16    /// Returns an optional list of scopes required to access this resource.
17    /// If this function returns `Some(scopes)`, the authenticated user’s token
18    /// must include **all** of the listed scopes.
19    /// If any are missing, the request will be rejected with a `403 Forbidden` response.
20    fn required_scopes(&self) -> Option<&Vec<String>> {
21        None
22    }
23
24    /// Returns the configured OAuth endpoints for this provider.
25    ///
26    /// - Key: endpoint path as a string (e.g., "/oauth/token")
27    /// - Value: corresponding `OauthEndpoint` configuration
28    ///
29    /// Returns `None` if no endpoints are configured.
30    fn auth_endpoints(&self) -> Option<&HashMap<String, OauthEndpoint>>;
31
32    /// Handles an incoming HTTP request for this authentication provider.
33    ///
34    /// This is the main entry point for processing OAuth requests,
35    /// such as token issuance, authorization code exchange, or revocation.
36    async fn handle_request(
37        &self,
38        request: http::Request<&str>,
39        state: Arc<McpAppState>,
40    ) -> Result<http::Response<GenericBody>, TransportServerError>;
41
42    /// Returns the `OauthEndpoint` associated with the given request path.
43    ///
44    /// This method looks up the request URI path in the endpoints returned by `auth_endpoints()`.
45    ///
46    /// ⚠️ Note:
47    /// - If your token and revocation endpoints share the same URL path (valid in some implementations),
48    ///   you may want to override this method to correctly distinguish the request type
49    ///   (e.g., based on request parameters like `grant_type` vs `token`).
50    fn endpoint_type(&self, request: &http::Request<&str>) -> Option<&OauthEndpoint> {
51        let endpoints = self.auth_endpoints()?;
52        endpoints.get(request.uri().path())
53    }
54
55    /// Returns the absolute URL of this resource's OAuth 2.0 Protected Resource Metadata document.
56    ///
57    /// This corresponds to the `resource_metadata` parameter defined in
58    /// [RFC 9531 - OAuth 2.0 Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9531).
59    ///
60    /// The returned URL is an **absolute** URL (including scheme and host), for example:
61    /// `https://api.example.com/.well-known/oauth-protected-resource`.
62    ///
63    fn protected_resource_metadata_url(&self) -> Option<&str>;
64
65    fn validate_allowed_methods(
66        &self,
67        endpoint: &OauthEndpoint,
68        method: &Method,
69    ) -> Option<http::Response<GenericBody>> {
70        let allowed_methods = match endpoint {
71            OauthEndpoint::AuthorizationEndpoint => {
72                vec![Method::GET, Method::HEAD, Method::OPTIONS]
73            }
74            OauthEndpoint::TokenEndpoint => vec![Method::POST, Method::OPTIONS],
75            OauthEndpoint::RegistrationEndpoint => vec![
76                Method::POST,
77                Method::GET,
78                Method::PUT,
79                Method::PATCH,
80                Method::DELETE,
81                Method::OPTIONS,
82            ],
83            OauthEndpoint::RevocationEndpoint => vec![Method::POST, Method::OPTIONS],
84            OauthEndpoint::IntrospectionEndpoint => vec![Method::POST, Method::OPTIONS],
85            OauthEndpoint::AuthorizationServerMetadata => {
86                vec![Method::GET, Method::HEAD, Method::OPTIONS]
87            }
88            OauthEndpoint::ProtectedResourceMetadata => {
89                vec![Method::GET, Method::HEAD, Method::OPTIONS]
90            }
91        };
92
93        if !allowed_methods.contains(method) {
94            return Some(GenericBody::create_405_response(method, &allowed_methods));
95        }
96        None
97    }
98}