Skip to main content

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