use crate::error::FastMCPError;
use crate::mcp::types::JsonRpcRequest;
use crate::server::middleware::{BoxFuture, Middleware, Next};
use async_trait::async_trait;
use std::sync::Arc;
pub mod oauth;
pub mod oidc;
pub mod providers;
#[derive(Debug, Clone, Default)]
pub struct AuthContext {
pub client_id: Option<String>,
pub user_id: Option<String>,
pub scopes: Vec<String>,
}
#[async_trait]
pub trait AuthProvider: Send + Sync {
async fn verify(&self, request: &JsonRpcRequest) -> Result<AuthContext, FastMCPError>;
}
pub struct SimpleAuthProvider {
expected_token: String,
}
impl SimpleAuthProvider {
pub fn new(token: &str) -> Self {
Self {
expected_token: token.to_string(),
}
}
}
#[async_trait]
impl AuthProvider for SimpleAuthProvider {
async fn verify(&self, request: &JsonRpcRequest) -> Result<AuthContext, FastMCPError> {
let token_opt = request
.transport_metadata
.as_ref()
.and_then(|metadata| {
metadata
.get("authorization")
.or_else(|| metadata.get("Authorization"))
})
.and_then(|h| h.strip_prefix("Bearer "));
if token_opt == Some(self.expected_token.as_str()) {
return Ok(AuthContext {
client_id: Some("user".to_string()),
user_id: Some("user".to_string()),
scopes: vec!["admin".to_string()],
});
}
let valid = if let Some(params) = &request.params {
if let Some(token) = params.get("token").and_then(|v| v.as_str()) {
token == self.expected_token
} else {
false
}
} else {
false
};
if valid {
Ok(AuthContext {
client_id: Some("user".to_string()),
user_id: Some("user".to_string()),
scopes: vec!["admin".to_string()],
})
} else {
Err(FastMCPError::InvalidRequest(
"Unauthorized: Invalid or missing token".to_string(),
))
}
}
}
tokio::task_local! {
static CURRENT_AUTH_CONTEXT: AuthContext;
}
pub fn current_context() -> Option<AuthContext> {
CURRENT_AUTH_CONTEXT.try_with(|ctx| ctx.clone()).ok()
}
pub struct AuthMiddleware {
provider: Arc<dyn AuthProvider>,
}
impl AuthMiddleware {
pub fn new(provider: Arc<dyn AuthProvider>) -> Self {
Self { provider }
}
}
impl Middleware for AuthMiddleware {
fn handle<'a, 'b>(
&'a self,
request: JsonRpcRequest,
next: Next<'b>,
) -> BoxFuture<'a, Result<crate::mcp::types::JsonRpcResponse, FastMCPError>>
where
'b: 'a,
{
Box::pin(async move {
let auth_context = self.provider.verify(&request).await?;
CURRENT_AUTH_CONTEXT
.scope(auth_context, next(request))
.await
})
}
}