auth_framework/server/core/
metadata.rs

1//! OAuth 2.0 Authorization Server Metadata (RFC 8414)
2//!
3//! This module implements RFC 8414, which defines a mechanism for clients
4//! to obtain configuration details about an OAuth 2.0 authorization server.
5
6use crate::errors::{AuthError, Result};
7// use crate::server::oauth2::OAuth2Server;
8// use crate::server::oauth21::OAuth21Server;
9use crate::oauth2_server::OAuth2Server; // Use the new OAuth2Server
10use serde::{Deserialize, Serialize};
11// use std::collections::HashSet;
12
13/// OAuth 2.0 Authorization Server Metadata
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuthorizationServerMetadata {
16    /// The authorization server's issuer identifier
17    pub issuer: String,
18
19    /// URL of the authorization server's authorization endpoint
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub authorization_endpoint: Option<String>,
22
23    /// URL of the authorization server's token endpoint
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub token_endpoint: Option<String>,
26
27    /// URL of the authorization server's JWK Set document
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub jwks_uri: Option<String>,
30
31    /// URL of the authorization server's registration endpoint
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub registration_endpoint: Option<String>,
34
35    /// List of scope values supported by the authorization server
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub scopes_supported: Option<Vec<String>>,
38
39    /// List of response type values supported by the authorization server
40    pub response_types_supported: Vec<String>,
41
42    /// List of response mode values supported by the authorization server
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub response_modes_supported: Option<Vec<String>>,
45
46    /// List of grant type values supported by the authorization server
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub grant_types_supported: Option<Vec<String>>,
49
50    /// List of client authentication methods supported by the token endpoint
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
53
54    /// List of client authentication methods supported by the revocation endpoint
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub revocation_endpoint_auth_methods_supported: Option<Vec<String>>,
57
58    /// List of client authentication methods supported by the introspection endpoint
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub introspection_endpoint_auth_methods_supported: Option<Vec<String>>,
61
62    /// List of PKCE code challenge methods supported
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub code_challenge_methods_supported: Option<Vec<String>>,
65
66    /// URL of the authorization server's revocation endpoint
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub revocation_endpoint: Option<String>,
69
70    /// URL of the authorization server's introspection endpoint
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub introspection_endpoint: Option<String>,
73
74    /// Boolean value indicating whether the authorization server provides the iss parameter
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub authorization_response_iss_parameter_supported: Option<bool>,
77
78    // RFC 9126 - Pushed Authorization Requests
79    /// URL of the pushed authorization request endpoint
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub pushed_authorization_request_endpoint: Option<String>,
82
83    /// Whether PAR is required for this authorization server
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub require_pushed_authorization_requests: Option<bool>,
86
87    // RFC 8628 - Device Authorization Grant
88    /// URL of the device authorization endpoint
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub device_authorization_endpoint: Option<String>,
91
92    // RFC 9449 - DPoP
93    /// List of algorithms supported for DPoP proof JWTs
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub dpop_signing_alg_values_supported: Option<Vec<String>>,
96
97    // RFC 8705 - Mutual TLS
98    /// URL of the authorization server's token endpoint for mutual TLS
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub mtls_endpoint_aliases: Option<MtlsEndpointAliases>,
101
102    /// List of client certificate types supported for mutual TLS
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub tls_client_certificate_bound_access_tokens: Option<bool>,
105
106    // OpenID Connect Discovery (if applicable)
107    /// URL of the authorization server's UserInfo endpoint
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub userinfo_endpoint: Option<String>,
110
111    /// List of subject identifier types supported
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub subject_types_supported: Option<Vec<String>>,
114
115    /// List of JWS signing algorithms supported for ID tokens
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub id_token_signing_alg_values_supported: Option<Vec<String>>,
118
119    /// List of JWE encryption algorithms supported for ID tokens
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
122
123    /// List of JWE encryption methods supported for ID tokens
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
126
127    /// List of claim names supported
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub claims_supported: Option<Vec<String>>,
130
131    /// Whether claims parameter is supported
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub claims_parameter_supported: Option<bool>,
134
135    /// Whether request parameter is supported
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub request_parameter_supported: Option<bool>,
138
139    /// Whether request_uri parameter is supported
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub request_uri_parameter_supported: Option<bool>,
142}
143
144/// Mutual TLS endpoint aliases
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct MtlsEndpointAliases {
147    /// Token endpoint for mutual TLS
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub token_endpoint: Option<String>,
150
151    /// Revocation endpoint for mutual TLS
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub revocation_endpoint: Option<String>,
154
155    /// Introspection endpoint for mutual TLS
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub introspection_endpoint: Option<String>,
158}
159
160/// Authorization Server Metadata Builder
161pub struct MetadataBuilder {
162    metadata: AuthorizationServerMetadata,
163}
164
165impl MetadataBuilder {
166    /// Create a new metadata builder
167    pub fn new(issuer: String) -> Self {
168        Self {
169            metadata: AuthorizationServerMetadata {
170                issuer,
171                authorization_endpoint: None,
172                token_endpoint: None,
173                jwks_uri: None,
174                registration_endpoint: None,
175                scopes_supported: None,
176                response_types_supported: vec!["code".to_string()],
177                response_modes_supported: None,
178                grant_types_supported: None,
179                token_endpoint_auth_methods_supported: None,
180                revocation_endpoint_auth_methods_supported: None,
181                introspection_endpoint_auth_methods_supported: None,
182                code_challenge_methods_supported: None,
183                revocation_endpoint: None,
184                introspection_endpoint: None,
185                authorization_response_iss_parameter_supported: None,
186                pushed_authorization_request_endpoint: None,
187                require_pushed_authorization_requests: None,
188                device_authorization_endpoint: None,
189                dpop_signing_alg_values_supported: None,
190                mtls_endpoint_aliases: None,
191                tls_client_certificate_bound_access_tokens: None,
192                userinfo_endpoint: None,
193                subject_types_supported: None,
194                id_token_signing_alg_values_supported: None,
195                id_token_encryption_alg_values_supported: None,
196                id_token_encryption_enc_values_supported: None,
197                claims_supported: None,
198                claims_parameter_supported: None,
199                request_parameter_supported: None,
200                request_uri_parameter_supported: None,
201            },
202        }
203    }
204
205    /// Set authorization endpoint
206    pub fn authorization_endpoint(mut self, endpoint: String) -> Self {
207        self.metadata.authorization_endpoint = Some(endpoint);
208        self
209    }
210
211    /// Set token endpoint
212    pub fn token_endpoint(mut self, endpoint: String) -> Self {
213        self.metadata.token_endpoint = Some(endpoint);
214        self
215    }
216
217    /// Set JWK Set URI
218    pub fn jwks_uri(mut self, uri: String) -> Self {
219        self.metadata.jwks_uri = Some(uri);
220        self
221    }
222
223    /// Set supported scopes
224    pub fn scopes_supported(mut self, scopes: Vec<String>) -> Self {
225        self.metadata.scopes_supported = Some(scopes);
226        self
227    }
228
229    /// Set supported response types
230    pub fn response_types_supported(mut self, response_types: Vec<String>) -> Self {
231        self.metadata.response_types_supported = response_types;
232        self
233    }
234
235    /// Set supported grant types
236    pub fn grant_types_supported(mut self, grant_types: Vec<String>) -> Self {
237        self.metadata.grant_types_supported = Some(grant_types);
238        self
239    }
240
241    /// Set supported token endpoint authentication methods
242    pub fn token_endpoint_auth_methods_supported(mut self, methods: Vec<String>) -> Self {
243        self.metadata.token_endpoint_auth_methods_supported = Some(methods);
244        self
245    }
246
247    /// Set supported PKCE code challenge methods
248    pub fn code_challenge_methods_supported(mut self, methods: Vec<String>) -> Self {
249        self.metadata.code_challenge_methods_supported = Some(methods);
250        self
251    }
252
253    /// Set revocation endpoint
254    pub fn revocation_endpoint(mut self, endpoint: String) -> Self {
255        self.metadata.revocation_endpoint = Some(endpoint);
256        self
257    }
258
259    /// Set introspection endpoint
260    pub fn introspection_endpoint(mut self, endpoint: String) -> Self {
261        self.metadata.introspection_endpoint = Some(endpoint);
262        self
263    }
264
265    /// Enable PAR (Pushed Authorization Requests)
266    pub fn enable_par(mut self, endpoint: String, required: bool) -> Self {
267        self.metadata.pushed_authorization_request_endpoint = Some(endpoint);
268        self.metadata.require_pushed_authorization_requests = Some(required);
269        self
270    }
271
272    /// Set device authorization endpoint
273    pub fn device_authorization_endpoint(mut self, endpoint: String) -> Self {
274        self.metadata.device_authorization_endpoint = Some(endpoint);
275        self
276    }
277
278    /// Enable DPoP support
279    pub fn enable_dpop(mut self, signing_algorithms: Vec<String>) -> Self {
280        self.metadata.dpop_signing_alg_values_supported = Some(signing_algorithms);
281        self
282    }
283
284    /// Enable Mutual TLS support
285    pub fn enable_mtls(
286        mut self,
287        mtls_endpoints: MtlsEndpointAliases,
288        certificate_bound_tokens: bool,
289    ) -> Self {
290        self.metadata.mtls_endpoint_aliases = Some(mtls_endpoints);
291        self.metadata.tls_client_certificate_bound_access_tokens = Some(certificate_bound_tokens);
292        self
293    }
294
295    /// Enable OpenID Connect support
296    pub fn enable_openid_connect(
297        mut self,
298        userinfo_endpoint: String,
299        subject_types: Vec<String>,
300        id_token_signing_algs: Vec<String>,
301    ) -> Self {
302        self.metadata.userinfo_endpoint = Some(userinfo_endpoint);
303        self.metadata.subject_types_supported = Some(subject_types);
304        self.metadata.id_token_signing_alg_values_supported = Some(id_token_signing_algs);
305        self
306    }
307
308    /// Build the metadata
309    pub fn build(self) -> AuthorizationServerMetadata {
310        self.metadata
311    }
312}
313
314/// Authorization Server Metadata Provider
315pub struct MetadataProvider {
316    metadata: AuthorizationServerMetadata,
317}
318
319impl MetadataProvider {
320    /// Create a new metadata provider
321    pub fn new(metadata: AuthorizationServerMetadata) -> Self {
322        Self { metadata }
323    }
324
325    /// Create metadata from OAuth 2.0 server configuration
326    pub fn from_oauth2_server(_server: &OAuth2Server, base_url: &str) -> Result<Self> {
327        let mut builder = MetadataBuilder::new(base_url.to_string())
328            .authorization_endpoint(format!("{}/oauth2/authorize", base_url))
329            .token_endpoint(format!("{}/oauth2/token", base_url))
330            .jwks_uri(format!("{}/.well-known/jwks.json", base_url))
331            .response_types_supported(vec!["code".to_string()])
332            .grant_types_supported(vec![
333                "authorization_code".to_string(),
334                "client_credentials".to_string(),
335                "refresh_token".to_string(),
336            ])
337            .token_endpoint_auth_methods_supported(vec![
338                "client_secret_basic".to_string(),
339                "client_secret_post".to_string(),
340            ])
341            .code_challenge_methods_supported(vec!["S256".to_string(), "plain".to_string()])
342            .revocation_endpoint(format!("{}/oauth2/revoke", base_url))
343            .introspection_endpoint(format!("{}/oauth2/introspect", base_url))
344            .scopes_supported(vec![
345                "openid".to_string(),
346                "profile".to_string(),
347                "email".to_string(),
348                "address".to_string(),
349                "phone".to_string(),
350            ]);
351
352        // Add device authorization if supported
353        builder = builder
354            .device_authorization_endpoint(format!("{}/oauth2/device_authorization", base_url));
355
356        // Add PAR if supported
357        builder = builder.enable_par(format!("{}/oauth2/par", base_url), false);
358
359        // Add DPoP if supported
360        builder = builder.enable_dpop(vec![
361            "ES256".to_string(),
362            "ES384".to_string(),
363            "ES512".to_string(),
364            "RS256".to_string(),
365        ]);
366
367        // Add Mutual TLS if supported
368        let mtls_endpoints = MtlsEndpointAliases {
369            token_endpoint: Some(format!("{}/oauth2/token", base_url)),
370            revocation_endpoint: Some(format!("{}/oauth2/revoke", base_url)),
371            introspection_endpoint: Some(format!("{}/oauth2/introspect", base_url)),
372        };
373        builder = builder.enable_mtls(mtls_endpoints, true);
374
375        Ok(Self::new(builder.build()))
376    }
377
378    /// Create metadata from OAuth 2.1 server configuration
379    pub fn from_oauth21_server(_server: &OAuth2Server, base_url: &str) -> Result<Self> {
380        // OAuth 2.1 uses the same base server but with enhanced security
381        let mut builder = MetadataBuilder::new(base_url.to_string())
382            .authorization_endpoint(format!("{}/oauth2/authorize", base_url))
383            .token_endpoint(format!("{}/oauth2/token", base_url))
384            .jwks_uri(format!("{}/.well-known/jwks.json", base_url))
385            .response_types_supported(vec!["code".to_string()]) // Only code in OAuth 2.1
386            .grant_types_supported(vec![
387                "authorization_code".to_string(),
388                "client_credentials".to_string(),
389                "refresh_token".to_string(),
390            ])
391            .token_endpoint_auth_methods_supported(vec![
392                "client_secret_basic".to_string(),
393                "client_secret_post".to_string(),
394                "tls_client_auth".to_string(),
395                "self_signed_tls_client_auth".to_string(),
396            ])
397            .code_challenge_methods_supported(vec!["S256".to_string()]) // Only S256 in OAuth 2.1
398            .revocation_endpoint(format!("{}/oauth2/revoke", base_url))
399            .introspection_endpoint(format!("{}/oauth2/introspect", base_url))
400            .scopes_supported(vec![
401                "openid".to_string(),
402                "profile".to_string(),
403                "email".to_string(),
404            ]);
405
406        // PAR is recommended in OAuth 2.1
407        builder = builder.enable_par(format!("{}/oauth2/par", base_url), true);
408
409        // DPoP is recommended in OAuth 2.1
410        builder = builder.enable_dpop(vec![
411            "ES256".to_string(),
412            "ES384".to_string(),
413            "ES512".to_string(),
414        ]);
415
416        // Add Mutual TLS if supported
417        let mtls_endpoints = MtlsEndpointAliases {
418            token_endpoint: Some(format!("{}/oauth2/token", base_url)),
419            revocation_endpoint: Some(format!("{}/oauth2/revoke", base_url)),
420            introspection_endpoint: Some(format!("{}/oauth2/introspect", base_url)),
421        };
422        builder = builder.enable_mtls(mtls_endpoints, true);
423
424        Ok(Self::new(builder.build()))
425    }
426
427    /// Get the authorization server metadata
428    pub fn get_metadata(&self) -> &AuthorizationServerMetadata {
429        &self.metadata
430    }
431
432    /// Get metadata as JSON
433    pub fn get_metadata_json(&self) -> Result<String> {
434        serde_json::to_string_pretty(&self.metadata).map_err(|e| {
435            AuthError::auth_method("metadata", format!("Failed to serialize metadata: {}", e))
436        })
437    }
438
439    /// Validate metadata completeness
440    pub fn validate(&self) -> Result<()> {
441        let mut errors = Vec::new();
442
443        // Required fields validation
444        if self.metadata.issuer.is_empty() {
445            errors.push("Issuer is required");
446        }
447
448        if self.metadata.response_types_supported.is_empty() {
449            errors.push("At least one response type must be supported");
450        }
451
452        // Validate URLs
453        let endpoints = [
454            &self.metadata.authorization_endpoint,
455            &self.metadata.token_endpoint,
456            &self.metadata.jwks_uri,
457            &self.metadata.revocation_endpoint,
458            &self.metadata.introspection_endpoint,
459        ];
460
461        for endpoint in endpoints.iter().filter_map(|ep| ep.as_ref()) {
462            if url::Url::parse(endpoint).is_err() {
463                errors.push("Invalid endpoint URL format");
464            }
465        }
466
467        // OAuth 2.1 specific validations
468        if self
469            .metadata
470            .code_challenge_methods_supported
471            .as_ref()
472            .is_some_and(|methods| methods.len() == 1 && methods[0] == "S256")
473        {
474            // This looks like OAuth 2.1, validate accordingly
475            if self.metadata.response_types_supported.len() != 1
476                || self.metadata.response_types_supported[0] != "code"
477            {
478                errors.push("OAuth 2.1 must only support 'code' response type");
479            }
480        }
481
482        if !errors.is_empty() {
483            return Err(AuthError::auth_method("metadata", errors.join(", ")));
484        }
485
486        Ok(())
487    }
488
489    /// Update metadata field
490    pub fn update_metadata<F>(&mut self, updater: F) -> Result<()>
491    where
492        F: FnOnce(&mut AuthorizationServerMetadata),
493    {
494        updater(&mut self.metadata);
495        self.validate()
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    #[test]
504    fn test_metadata_builder() {
505        let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
506            .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
507            .token_endpoint("https://auth.example.com/oauth2/token".to_string())
508            .grant_types_supported(vec!["authorization_code".to_string()])
509            .code_challenge_methods_supported(vec!["S256".to_string()])
510            .build();
511
512        assert_eq!(metadata.issuer, "https://auth.example.com");
513        assert_eq!(
514            metadata.authorization_endpoint,
515            Some("https://auth.example.com/oauth2/authorize".to_string())
516        );
517        assert_eq!(
518            metadata.grant_types_supported,
519            Some(vec!["authorization_code".to_string()])
520        );
521    }
522
523    #[test]
524    fn test_metadata_provider() {
525        let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
526            .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
527            .token_endpoint("https://auth.example.com/oauth2/token".to_string())
528            .build();
529
530        let provider = MetadataProvider::new(metadata);
531        let json = provider.get_metadata_json().unwrap();
532
533        assert!(json.contains("https://auth.example.com"));
534        assert!(json.contains("authorization_endpoint"));
535    }
536
537    #[test]
538    fn test_metadata_validation() {
539        let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
540            .authorization_endpoint("https://auth.example.com/oauth2/authorize".to_string())
541            .token_endpoint("https://auth.example.com/oauth2/token".to_string())
542            .build();
543
544        let provider = MetadataProvider::new(metadata);
545        provider.validate().unwrap();
546    }
547
548    #[test]
549    fn test_oauth21_specific_metadata() {
550        let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
551            .response_types_supported(vec!["code".to_string()])
552            .code_challenge_methods_supported(vec!["S256".to_string()])
553            .enable_par("https://auth.example.com/oauth2/par".to_string(), true)
554            .enable_dpop(vec!["ES256".to_string()])
555            .build();
556
557        let provider = MetadataProvider::new(metadata);
558        provider.validate().unwrap();
559
560        let metadata = provider.get_metadata();
561        assert_eq!(metadata.require_pushed_authorization_requests, Some(true));
562        assert!(metadata.dpop_signing_alg_values_supported.is_some());
563    }
564
565    #[test]
566    fn test_mtls_metadata() {
567        let mtls_endpoints = MtlsEndpointAliases {
568            token_endpoint: Some("https://mtls.auth.example.com/oauth2/token".to_string()),
569            revocation_endpoint: Some("https://mtls.auth.example.com/oauth2/revoke".to_string()),
570            introspection_endpoint: Some(
571                "https://mtls.auth.example.com/oauth2/introspect".to_string(),
572            ),
573        };
574
575        let metadata = MetadataBuilder::new("https://auth.example.com".to_string())
576            .enable_mtls(mtls_endpoints, true)
577            .build();
578
579        assert!(metadata.mtls_endpoint_aliases.is_some());
580        assert_eq!(
581            metadata.tls_client_certificate_bound_access_tokens,
582            Some(true)
583        );
584    }
585}
586
587