turbomcp_auth/
config.rs

1//! Authentication Configuration Types
2//!
3//! This module contains all configuration structures for the TurboMCP authentication system.
4
5use std::collections::HashMap;
6use std::sync::Arc;
7#[cfg(feature = "dpop")]
8use std::time::Duration;
9
10use secrecy::{ExposeSecret, SecretString};
11use serde::{Deserialize, Serialize};
12use tokio::sync::RwLock;
13
14use turbomcp_protocol::{Error as McpError, Result as McpResult};
15
16// DPoP support (feature-gated)
17#[cfg(feature = "dpop")]
18use super::dpop::DpopAlgorithm;
19
20/// Authentication configuration
21///
22/// # MCP Compliance
23///
24/// Per MCP specification (2025-06-18), authentication is **stateless**.
25/// All authentication is token-based with validation on every request.
26/// No server-side session state is maintained.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct AuthConfig {
29    /// Enable authentication
30    pub enabled: bool,
31    /// Authentication provider configuration
32    pub providers: Vec<AuthProviderConfig>,
33    /// Authorization configuration
34    pub authorization: AuthorizationConfig,
35}
36
37/// Authentication provider configuration
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct AuthProviderConfig {
40    /// Provider name
41    pub name: String,
42    /// Provider type
43    pub provider_type: AuthProviderType,
44    /// Provider-specific settings
45    pub settings: HashMap<String, serde_json::Value>,
46    /// Whether this provider is enabled
47    pub enabled: bool,
48    /// Priority (lower number = higher priority)
49    pub priority: u32,
50}
51
52/// Authentication provider types
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54pub enum AuthProviderType {
55    /// OAuth 2.0 provider
56    OAuth2,
57    /// API key provider
58    ApiKey,
59    /// JWT token provider
60    Jwt,
61    /// Custom authentication provider
62    Custom,
63}
64
65/// Security levels for OAuth 2.1 flows
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
67pub enum SecurityLevel {
68    /// Standard OAuth 2.1 with PKCE
69    #[default]
70    Standard,
71    /// Enhanced security with DPoP token binding
72    Enhanced,
73    /// Maximum security with full DPoP
74    Maximum,
75}
76
77/// DPoP (Demonstration of Proof-of-Possession) configuration
78#[cfg(feature = "dpop")]
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DpopConfig {
81    /// Cryptographic algorithm for DPoP proofs
82    pub key_algorithm: DpopAlgorithm,
83    /// Proof lifetime in seconds (default: 60s per RFC 9449)
84    #[serde(default = "default_proof_lifetime")]
85    pub proof_lifetime: Duration,
86    /// Maximum clock skew tolerance in seconds (default: 300s per RFC 9449)
87    #[serde(default = "default_clock_skew")]
88    pub clock_skew_tolerance: Duration,
89    /// Key storage backend selection
90    #[serde(default)]
91    pub key_storage: DpopKeyStorageConfig,
92}
93
94#[cfg(feature = "dpop")]
95fn default_proof_lifetime() -> Duration {
96    Duration::from_secs(60)
97}
98
99#[cfg(feature = "dpop")]
100fn default_clock_skew() -> Duration {
101    Duration::from_secs(300)
102}
103
104/// DPoP key storage configuration
105#[cfg(feature = "dpop")]
106#[derive(Debug, Clone, Serialize, Deserialize, Default)]
107pub enum DpopKeyStorageConfig {
108    /// In-memory storage (development)
109    #[default]
110    Memory,
111    /// Redis storage (production)
112    Redis {
113        /// Redis connection URL
114        url: String,
115    },
116    /// HSM storage (high security)
117    Hsm {
118        /// HSM configuration parameters
119        config: serde_json::Value,
120    },
121}
122
123#[cfg(feature = "dpop")]
124impl Default for DpopConfig {
125    fn default() -> Self {
126        Self {
127            key_algorithm: DpopAlgorithm::ES256,
128            proof_lifetime: default_proof_lifetime(),
129            clock_skew_tolerance: default_clock_skew(),
130            key_storage: DpopKeyStorageConfig::default(),
131        }
132    }
133}
134
135/// Authorization configuration
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct AuthorizationConfig {
138    /// Enable role-based access control
139    pub rbac_enabled: bool,
140    /// Default roles for new users
141    pub default_roles: Vec<String>,
142    /// Permission inheritance rules
143    pub inheritance_rules: HashMap<String, Vec<String>>,
144    /// Resource-based permissions
145    pub resource_permissions: HashMap<String, Vec<String>>,
146}
147
148/// OAuth 2.1 configuration
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct OAuth2Config {
151    /// Client ID
152    pub client_id: String,
153    /// Client secret (stored securely with automatic zeroization on drop)
154    #[serde(
155        serialize_with = "serialize_secret",
156        deserialize_with = "deserialize_secret"
157    )]
158    pub client_secret: SecretString,
159    /// Authorization endpoint
160    pub auth_url: String,
161    /// Token endpoint
162    pub token_url: String,
163    /// Token revocation endpoint (RFC 7009) - optional but recommended
164    #[serde(default)]
165    pub revocation_url: Option<String>,
166    /// Redirect URI
167    pub redirect_uri: String,
168    /// Scopes to request
169    pub scopes: Vec<String>,
170    /// OAuth 2.1 flow type
171    pub flow_type: OAuth2FlowType,
172    /// Additional parameters
173    pub additional_params: HashMap<String, String>,
174    /// Security level for OAuth flow
175    #[serde(default)]
176    pub security_level: SecurityLevel,
177    /// DPoP configuration (when security_level is Enhanced or Maximum)
178    #[cfg(feature = "dpop")]
179    #[serde(default)]
180    pub dpop_config: Option<DpopConfig>,
181    /// MCP server canonical URI for Resource Indicators (RFC 8707)
182    /// This is the target resource server URI that tokens will be bound to
183    #[serde(default)]
184    pub mcp_resource_uri: Option<String>,
185    /// Automatic Resource Indicator mode - when true, resource parameter
186    /// is automatically included in all OAuth flows for MCP compliance
187    #[serde(default = "default_auto_resource_indicators")]
188    pub auto_resource_indicators: bool,
189}
190
191// Custom serialization for SecretString
192fn serialize_secret<S>(secret: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
193where
194    S: serde::Serializer,
195{
196    serializer.serialize_str(secret.expose_secret())
197}
198
199// Custom deserialization for SecretString
200fn deserialize_secret<'de, D>(deserializer: D) -> Result<SecretString, D::Error>
201where
202    D: serde::Deserializer<'de>,
203{
204    let s: String = serde::Deserialize::deserialize(deserializer)?;
205    Ok(SecretString::new(s))
206}
207
208/// Default auto resource indicators setting (enabled for MCP compliance)
209fn default_auto_resource_indicators() -> bool {
210    true
211}
212
213/// OAuth 2.1 flow types
214#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
215pub enum OAuth2FlowType {
216    /// Authorization Code flow
217    AuthorizationCode,
218    /// Client Credentials flow
219    ClientCredentials,
220    /// Device Authorization flow
221    DeviceCode,
222    /// Implicit flow (not recommended)
223    Implicit,
224}
225
226/// OAuth 2.1 authorization result
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct OAuth2AuthResult {
229    /// Authorization URL for user
230    pub auth_url: String,
231    /// State parameter for CSRF protection
232    pub state: String,
233    /// Code verifier for PKCE
234    pub code_verifier: Option<String>,
235    /// Device code (for device flow)
236    pub device_code: Option<String>,
237    /// User code (for device flow)
238    pub user_code: Option<String>,
239    /// Verification URL (for device flow)
240    pub verification_uri: Option<String>,
241}
242
243/// Protected Resource Metadata (RFC 9728) for server-side discovery
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct ProtectedResourceMetadata {
246    /// Resource server identifier (REQUIRED)
247    pub resource: String,
248    /// Authorization server endpoint (REQUIRED)
249    pub authorization_server: String,
250    /// Available scopes for this resource (OPTIONAL)
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub scopes_supported: Option<Vec<String>>,
253    /// Bearer token methods supported (OPTIONAL)
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub bearer_methods_supported: Option<Vec<BearerTokenMethod>>,
256    /// Resource documentation URI (OPTIONAL)
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub resource_documentation: Option<String>,
259    /// Additional metadata (OPTIONAL)
260    #[serde(flatten)]
261    pub additional_metadata: HashMap<String, serde_json::Value>,
262}
263
264/// Bearer token delivery methods (RFC 9728)
265#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
266#[serde(rename_all = "lowercase")]
267pub enum BearerTokenMethod {
268    /// Authorization header (RFC 6750)
269    #[default]
270    Header,
271    /// Query parameter (RFC 6750) - discouraged for security
272    Query,
273    /// Request body (RFC 6750) - for POST requests only
274    Body,
275}
276
277/// MCP Server Resource Registry for RFC 9728 compliance
278#[derive(Debug, Clone)]
279pub struct McpResourceRegistry {
280    /// Map of resource URI to metadata
281    resources: Arc<RwLock<HashMap<String, ProtectedResourceMetadata>>>,
282    /// Default authorization server for new resources
283    default_auth_server: String,
284    /// Base resource URI for this MCP server
285    base_resource_uri: String,
286}
287
288impl McpResourceRegistry {
289    /// Create a new MCP resource registry
290    #[must_use]
291    pub fn new(base_resource_uri: String, auth_server: String) -> Self {
292        Self {
293            resources: Arc::new(RwLock::new(HashMap::new())),
294            default_auth_server: auth_server,
295            base_resource_uri,
296        }
297    }
298
299    /// Register a protected resource (RFC 9728)
300    pub async fn register_resource(
301        &self,
302        resource_id: &str,
303        scopes: Vec<String>,
304        documentation: Option<String>,
305    ) -> McpResult<()> {
306        let resource_uri = format!(
307            "{}/{}",
308            self.base_resource_uri.trim_end_matches('/'),
309            resource_id
310        );
311
312        let metadata = ProtectedResourceMetadata {
313            resource: resource_uri.clone(),
314            authorization_server: self.default_auth_server.clone(),
315            scopes_supported: Some(scopes),
316            bearer_methods_supported: Some(vec![
317                BearerTokenMethod::Header, // Primary method
318                BearerTokenMethod::Body,   // For POST requests
319            ]),
320            resource_documentation: documentation,
321            additional_metadata: HashMap::new(),
322        };
323
324        self.resources.write().await.insert(resource_uri, metadata);
325        Ok(())
326    }
327
328    /// Get metadata for a specific resource
329    pub async fn get_resource_metadata(
330        &self,
331        resource_uri: &str,
332    ) -> Option<ProtectedResourceMetadata> {
333        self.resources.read().await.get(resource_uri).cloned()
334    }
335
336    /// List all registered resources
337    pub async fn list_resources(&self) -> Vec<String> {
338        self.resources.read().await.keys().cloned().collect()
339    }
340
341    /// Generate RFC 9728 compliant metadata for well-known endpoint
342    pub async fn generate_well_known_metadata(&self) -> HashMap<String, ProtectedResourceMetadata> {
343        self.resources.read().await.clone()
344    }
345
346    /// Validate that a token has required scope for resource access
347    pub async fn validate_scope_for_resource(
348        &self,
349        resource_uri: &str,
350        token_scopes: &[String],
351    ) -> McpResult<bool> {
352        if let Some(metadata) = self.get_resource_metadata(resource_uri).await {
353            if let Some(required_scopes) = metadata.scopes_supported {
354                // Check if token has at least one required scope
355                let has_required_scope = required_scopes
356                    .iter()
357                    .any(|scope| token_scopes.contains(scope));
358                Ok(has_required_scope)
359            } else {
360                // No specific scopes required
361                Ok(true)
362            }
363        } else {
364            Err(McpError::validation(format!(
365                "Unknown resource: {}",
366                resource_uri
367            )))
368        }
369    }
370}
371
372/// Dynamic Client Registration Request (RFC 7591)
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct ClientRegistrationRequest {
375    /// Client metadata - redirect URIs (REQUIRED for authorization code flow)
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub redirect_uris: Option<Vec<String>>,
378    /// Client metadata - response types
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub response_types: Option<Vec<String>>,
381    /// Client metadata - grant types
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub grant_types: Option<Vec<String>>,
384    /// Application type (web, native)
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub application_type: Option<ApplicationType>,
387    /// Human-readable client name
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub client_name: Option<String>,
390    /// Client URI for information
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub client_uri: Option<String>,
393    /// Logo URI
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub logo_uri: Option<String>,
396    /// Scope string with space-delimited scopes
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub scope: Option<String>,
399    /// Contacts (email addresses)
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub contacts: Option<Vec<String>>,
402    /// Terms of service URI
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub tos_uri: Option<String>,
405    /// Privacy policy URI
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub policy_uri: Option<String>,
408    /// Software ID for client
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub software_id: Option<String>,
411    /// Software version
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub software_version: Option<String>,
414}
415
416/// Dynamic Client Registration Response (RFC 7591)
417#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct ClientRegistrationResponse {
419    /// Unique client identifier (REQUIRED)
420    pub client_id: String,
421    /// Client secret (OPTIONAL - not provided for public clients)
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub client_secret: Option<String>,
424    /// Registration access token for client configuration endpoint
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub registration_access_token: Option<String>,
427    /// Client configuration endpoint
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub registration_client_uri: Option<String>,
430    /// Client ID issued at timestamp
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub client_id_issued_at: Option<i64>,
433    /// Client secret expires at timestamp (REQUIRED if client_secret provided)
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub client_secret_expires_at: Option<i64>,
436    /// Confirmed client metadata - redirect URIs
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub redirect_uris: Option<Vec<String>>,
439    /// Confirmed response types
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub response_types: Option<Vec<String>>,
442    /// Confirmed grant types
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub grant_types: Option<Vec<String>>,
445    /// Confirmed application type
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub application_type: Option<ApplicationType>,
448    /// Confirmed client name
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub client_name: Option<String>,
451    /// Confirmed scope
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub scope: Option<String>,
454}
455
456/// Application type for OAuth client (RFC 7591)
457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
458#[serde(rename_all = "lowercase")]
459pub enum ApplicationType {
460    /// Web application - runs on web server, can keep secrets
461    #[default]
462    Web,
463    /// Native application - mobile/desktop app, cannot keep secrets
464    Native,
465}
466
467/// Client Registration Error Response (RFC 7591)
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct ClientRegistrationError {
470    /// Error code
471    pub error: ClientRegistrationErrorCode,
472    /// Human-readable error description
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub error_description: Option<String>,
475}
476
477/// Client Registration Error Codes (RFC 7591)
478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
479#[serde(rename_all = "snake_case")]
480pub enum ClientRegistrationErrorCode {
481    /// The value of one or more redirect_uris is invalid
482    InvalidRedirectUri,
483    /// The value of one of the client metadata fields is invalid
484    InvalidClientMetadata,
485    /// The software statement presented is invalid
486    InvalidSoftwareStatement,
487    /// The software statement cannot be checked
488    UnapprovedSoftwareStatement,
489}
490
491/// Dynamic Client Registration Manager for RFC 7591 compliance
492#[derive(Debug, Clone)]
493pub struct DynamicClientRegistration {
494    /// Registration endpoint URL
495    registration_endpoint: String,
496    /// Default application type for new registrations
497    default_application_type: ApplicationType,
498    /// Default grant types
499    default_grant_types: Vec<String>,
500    /// Default response types
501    default_response_types: Vec<String>,
502    /// HTTP client for registration requests
503    client: reqwest::Client,
504}
505
506impl DynamicClientRegistration {
507    /// Create a new dynamic client registration manager
508    #[must_use]
509    pub fn new(registration_endpoint: String) -> Self {
510        Self {
511            registration_endpoint,
512            default_application_type: ApplicationType::Web,
513            default_grant_types: vec!["authorization_code".to_string()],
514            default_response_types: vec!["code".to_string()],
515            client: reqwest::Client::new(),
516        }
517    }
518
519    /// Register a new OAuth client dynamically (RFC 7591)
520    pub async fn register_client(
521        &self,
522        request: ClientRegistrationRequest,
523    ) -> McpResult<ClientRegistrationResponse> {
524        // Prepare registration request with defaults
525        let mut registration_request = request;
526
527        // Apply defaults if not specified
528        if registration_request.application_type.is_none() {
529            registration_request.application_type = Some(self.default_application_type.clone());
530        }
531        if registration_request.grant_types.is_none() {
532            registration_request.grant_types = Some(self.default_grant_types.clone());
533        }
534        if registration_request.response_types.is_none() {
535            registration_request.response_types = Some(self.default_response_types.clone());
536        }
537
538        // Send registration request
539        let response = self
540            .client
541            .post(&self.registration_endpoint)
542            .header("Content-Type", "application/json")
543            .json(&registration_request)
544            .send()
545            .await
546            .map_err(|e| McpError::validation(format!("Registration request failed: {}", e)))?;
547
548        // Handle response
549        if response.status().is_success() {
550            let registration_response: ClientRegistrationResponse =
551                response.json().await.map_err(|e| {
552                    McpError::validation(format!("Invalid registration response: {}", e))
553                })?;
554            Ok(registration_response)
555        } else {
556            // Parse error response
557            let error_response: ClientRegistrationError = response
558                .json()
559                .await
560                .map_err(|e| McpError::validation(format!("Invalid error response: {}", e)))?;
561            Err(McpError::validation(format!(
562                "Client registration failed: {} - {}",
563                error_response.error as u32,
564                error_response.error_description.unwrap_or_default()
565            )))
566        }
567    }
568
569    /// Create a default MCP client registration request
570    #[must_use]
571    pub fn create_mcp_client_request(
572        client_name: &str,
573        redirect_uris: Vec<String>,
574        mcp_server_uri: &str,
575    ) -> ClientRegistrationRequest {
576        ClientRegistrationRequest {
577            redirect_uris: Some(redirect_uris),
578            response_types: Some(vec!["code".to_string()]),
579            grant_types: Some(vec!["authorization_code".to_string()]),
580            application_type: Some(ApplicationType::Web),
581            client_name: Some(format!("MCP Client: {}", client_name)),
582            client_uri: Some(mcp_server_uri.to_string()),
583            scope: Some(
584                "mcp:tools:read mcp:tools:execute mcp:resources:read mcp:prompts:read".to_string(),
585            ),
586            software_id: Some("turbomcp".to_string()),
587            software_version: Some(env!("CARGO_PKG_VERSION").to_string()),
588            logo_uri: None,
589            contacts: None,
590            tos_uri: None,
591            policy_uri: None,
592        }
593    }
594}
595
596/// Device authorization response for CLI/IoT flows
597#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct DeviceAuthorizationResponse {
599    /// Device verification code
600    pub device_code: String,
601    /// User-friendly verification code
602    pub user_code: String,
603    /// Verification URI
604    pub verification_uri: String,
605    /// Complete verification URI (optional)
606    pub verification_uri_complete: Option<String>,
607    /// Expires in seconds
608    pub expires_in: u64,
609    /// Polling interval in seconds
610    pub interval: u64,
611}
612
613/// Provider-specific configuration for handling OAuth quirks
614#[derive(Debug, Clone)]
615pub struct ProviderConfig {
616    /// Provider type (Google, Microsoft, GitHub, etc.)
617    pub provider_type: ProviderType,
618    /// Custom scopes required by provider
619    pub default_scopes: Vec<String>,
620    /// Provider-specific token refresh behavior
621    pub refresh_behavior: RefreshBehavior,
622    /// Custom userinfo endpoint
623    pub userinfo_endpoint: Option<String>,
624    /// Additional provider-specific parameters
625    pub additional_params: HashMap<String, String>,
626}
627
628/// OAuth2 provider types with built-in configurations
629#[derive(Debug, Clone, PartialEq)]
630pub enum ProviderType {
631    /// Google OAuth2 provider
632    Google,
633    /// Microsoft/Azure OAuth2 provider
634    Microsoft,
635    /// GitHub OAuth2 provider
636    GitHub,
637    /// GitLab OAuth2 provider
638    GitLab,
639    /// Apple Sign In provider (OAuth 2.1 with custom requirements)
640    Apple,
641    /// Okta enterprise OAuth2 provider
642    Okta,
643    /// Auth0 identity platform provider
644    Auth0,
645    /// Keycloak open-source OIDC provider
646    Keycloak,
647    /// Generic OAuth2 provider with standard scopes
648    Generic,
649    /// Custom provider with custom configuration
650    Custom(String),
651}
652
653/// Token refresh behavior strategies
654#[derive(Debug, Clone)]
655pub enum RefreshBehavior {
656    /// Always refresh tokens before expiration
657    Proactive,
658    /// Only refresh when token is actually expired
659    Reactive,
660    /// Custom refresh logic
661    Custom,
662}