turbomcp_auth/providers/
api_key.rs

1//! API Key Authentication Provider
2//!
3//! Simple API key-based authentication for service-to-service communication.
4//!
5//! ## Security
6//!
7//! This provider uses constant-time comparison to prevent timing attacks on API keys.
8//! See [`crate::api_key_validation`] for implementation details.
9
10use std::collections::HashMap;
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use tokio::sync::RwLock;
15
16use super::super::api_key_validation::validate_api_key;
17use super::super::config::AuthProviderType;
18use super::super::context::AuthContext;
19use super::super::types::{AuthCredentials, AuthProvider, TokenInfo, UserInfo};
20use turbomcp_protocol::{Error as McpError, Result as McpResult};
21
22/// API Key authentication provider
23#[derive(Debug)]
24pub struct ApiKeyProvider {
25    /// Provider name
26    name: String,
27    /// Valid API keys with associated user info
28    api_keys: Arc<RwLock<HashMap<String, UserInfo>>>,
29}
30
31impl ApiKeyProvider {
32    /// Create a new API key provider
33    #[must_use]
34    pub fn new(name: String) -> Self {
35        Self {
36            name,
37            api_keys: Arc::new(RwLock::new(HashMap::new())),
38        }
39    }
40
41    /// Add an API key
42    pub async fn add_api_key(&self, key: String, user_info: UserInfo) {
43        self.api_keys.write().await.insert(key, user_info);
44    }
45
46    /// Remove an API key
47    pub async fn remove_api_key(&self, key: &str) -> bool {
48        self.api_keys.write().await.remove(key).is_some()
49    }
50
51    /// List all API keys (returns keys only, not full info for security)
52    pub async fn list_api_keys(&self) -> Vec<String> {
53        self.api_keys.read().await.keys().cloned().collect()
54    }
55}
56
57#[async_trait]
58impl AuthProvider for ApiKeyProvider {
59    fn name(&self) -> &str {
60        &self.name
61    }
62
63    fn provider_type(&self) -> AuthProviderType {
64        AuthProviderType::ApiKey
65    }
66
67    async fn authenticate(&self, credentials: AuthCredentials) -> McpResult<AuthContext> {
68        match credentials {
69            AuthCredentials::ApiKey { key } => {
70                let api_keys = self.api_keys.read().await;
71
72                // Use constant-time comparison to prevent timing attacks
73                // Instead of HashMap::get (which uses string equality), we iterate
74                // and use secure comparison for each key
75                let mut matched_user_info: Option<UserInfo> = None;
76
77                for (stored_key, user_info) in api_keys.iter() {
78                    if validate_api_key(&key, stored_key) {
79                        matched_user_info = Some(user_info.clone());
80                        break;
81                    }
82                }
83
84                if let Some(user_info) = matched_user_info {
85                    let token = TokenInfo {
86                        access_token: key,
87                        token_type: "ApiKey".to_string(),
88                        refresh_token: None,
89                        expires_in: None,
90                        scope: None,
91                    };
92
93                    AuthContext::builder()
94                        .subject(user_info.id.clone())
95                        .user(user_info.clone())
96                        .roles(vec!["api_user".to_string()])
97                        .permissions(vec!["api_access".to_string()])
98                        .request_id(uuid::Uuid::new_v4().to_string())
99                        .token(token)
100                        .provider(self.name.clone())
101                        .build()
102                        .map_err(|e| McpError::internal(e.to_string()))
103                } else {
104                    Err(McpError::internal("Invalid API key".to_string()))
105                }
106            }
107            _ => Err(McpError::internal(
108                "Invalid credentials for API key provider".to_string(),
109            )),
110        }
111    }
112
113    async fn validate_token(&self, token: &str) -> McpResult<AuthContext> {
114        self.authenticate(AuthCredentials::ApiKey {
115            key: token.to_string(),
116        })
117        .await
118    }
119
120    async fn refresh_token(&self, _refresh_token: &str) -> McpResult<TokenInfo> {
121        Err(McpError::internal(
122            "API keys do not support token refresh".to_string(),
123        ))
124    }
125
126    async fn revoke_token(&self, token: &str) -> McpResult<()> {
127        let removed = self.remove_api_key(token).await;
128        if removed {
129            Ok(())
130        } else {
131            Err(McpError::internal("API key not found".to_string()))
132        }
133    }
134
135    async fn get_user_info(&self, token: &str) -> McpResult<UserInfo> {
136        let api_keys = self.api_keys.read().await;
137
138        // Use constant-time comparison to prevent timing attacks
139        for (stored_key, user_info) in api_keys.iter() {
140            if validate_api_key(token, stored_key) {
141                return Ok(user_info.clone());
142            }
143        }
144
145        Err(McpError::internal("Invalid API key".to_string()))
146    }
147}