Skip to main content

mcp_kit/auth/
bearer.rs

1use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc};
2
3use crate::{
4    auth::{
5        credentials::Credentials,
6        identity::AuthenticatedIdentity,
7        provider::{AuthFuture, AuthProvider},
8    },
9    error::{McpError, McpResult},
10};
11
12/// A custom async validator function for bearer tokens.
13pub type BearerValidatorFn = Arc<
14    dyn Fn(String) -> Pin<Box<dyn Future<Output = McpResult<AuthenticatedIdentity>> + Send>>
15        + Send
16        + Sync,
17>;
18
19enum Inner {
20    /// Static allow-list: any token in the set is accepted; identity subject = token.
21    Static(HashSet<String>),
22    /// Custom async validator.
23    Custom(BearerValidatorFn),
24}
25
26/// Validates `Authorization: Bearer <token>` credentials.
27///
28/// # Examples
29///
30/// Static token list:
31/// ```rust,no_run
32/// use mcp_kit::auth::BearerTokenProvider;
33///
34/// let provider = BearerTokenProvider::new(["my-secret-token", "other-token"]);
35/// ```
36///
37/// Custom async validator:
38/// ```rust,no_run
39/// use mcp_kit::auth::{BearerTokenProvider, AuthenticatedIdentity};
40///
41/// let provider = BearerTokenProvider::with_validator(|token| async move {
42///     if token == "valid" {
43///         Ok(AuthenticatedIdentity::new("alice").with_scopes(["tools:execute"]))
44///     } else {
45///         Err(mcp_kit::McpError::Unauthorized("bad token".into()))
46///     }
47/// });
48/// ```
49pub struct BearerTokenProvider {
50    inner: Inner,
51}
52
53impl BearerTokenProvider {
54    /// Create a provider that accepts any token in the given iterator.
55    /// The identity subject is set to the token value itself.
56    pub fn new(tokens: impl IntoIterator<Item = impl Into<String>>) -> Self {
57        Self {
58            inner: Inner::Static(tokens.into_iter().map(Into::into).collect()),
59        }
60    }
61
62    /// Create a provider with a custom async validator function.
63    pub fn with_validator<F, Fut>(f: F) -> Self
64    where
65        F: Fn(String) -> Fut + Send + Sync + 'static,
66        Fut: Future<Output = McpResult<AuthenticatedIdentity>> + Send + 'static,
67    {
68        Self {
69            inner: Inner::Custom(Arc::new(move |token| Box::pin(f(token)))),
70        }
71    }
72}
73
74impl AuthProvider for BearerTokenProvider {
75    fn authenticate<'a>(&'a self, credentials: &'a Credentials) -> AuthFuture<'a> {
76        Box::pin(async move {
77            match credentials {
78                Credentials::Bearer { token } => match &self.inner {
79                    Inner::Static(set) => {
80                        if set.contains(token.as_str()) {
81                            Ok(AuthenticatedIdentity::new(token.clone()))
82                        } else {
83                            Err(McpError::Unauthorized("invalid bearer token".into()))
84                        }
85                    }
86                    Inner::Custom(f) => f(token.clone()).await,
87                },
88                _ => Err(McpError::Unauthorized("expected bearer token".into())),
89            }
90        })
91    }
92
93    fn accepts(&self, credentials: &Credentials) -> bool {
94        matches!(credentials, Credentials::Bearer { .. })
95    }
96}