fix: default to allowing missing issuer (#1051)

This commit is contained in:
Jack Amadeo 2026-07-27 10:35:03 -04:00 • committed by GitHub
commit 92c0793247
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1000,6 +1000,7 @@ pub struct AuthorizationManager {
resource_scopes: RwLock<Vec<String>>, resource_scopes: RwLock<Vec<String>>,
/// OIDC Dynamic Client Registration `application_type` (SEP-837) /// OIDC Dynamic Client Registration `application_type` (SEP-837)
application_type: Option<String>, application_type: Option<String>,
strict_issuer_validation: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -1245,6 +1246,7 @@ impl AuthorizationManager {
www_auth_scopes: RwLock::new(Vec::new()), www_auth_scopes: RwLock::new(Vec::new()),
resource_scopes: RwLock::new(Vec::new()), resource_scopes: RwLock::new(Vec::new()),
application_type: Some(DEFAULT_APPLICATION_TYPE.to_string()), application_type: Some(DEFAULT_APPLICATION_TYPE.to_string()),
strict_issuer_validation: false,
}; };
Ok(manager) Ok(manager)
@ -1255,6 +1257,17 @@ impl AuthorizationManager {
self.scope_upgrade_config = config; self.scope_upgrade_config = config;
} }
/// Configure whether authorization server metadata discovery requires the
/// metadata `issuer` field.
///
/// The default is `false` to preserve compatibility with legacy
/// authorization servers that omit `issuer`. Set this to `true` to enforce
/// the RFC 8414/OIDC requirement that discovered metadata include `issuer`
/// whenever the expected issuer can be derived from the discovery URL.
pub fn set_strict_issuer_validation(&mut self, strict: bool) {
self.strict_issuer_validation = strict;
}
/// Set a custom credential store /// Set a custom credential store
/// ///
/// This allows you to provide your own implementation of credential storage, /// This allows you to provide your own implementation of credential storage,
@ -2226,7 +2239,7 @@ impl AuthorizationManager {
match serde_json::from_slice::<AuthorizationMetadata>(response.body()) { match serde_json::from_slice::<AuthorizationMetadata>(response.body()) {
Ok(metadata) => { Ok(metadata) => {
Self::validate_authorization_metadata_issuer(discovery_url, &metadata)?; self.validate_authorization_metadata_issuer(discovery_url, &metadata)?;
Ok(Some(metadata)) Ok(Some(metadata))
} }
Err(err) => { Err(err) => {
@ -2288,6 +2301,7 @@ impl AuthorizationManager {
} }
fn validate_authorization_metadata_issuer( fn validate_authorization_metadata_issuer(
&self,
discovery_url: &Url, discovery_url: &Url,
metadata: &AuthorizationMetadata, metadata: &AuthorizationMetadata,
) -> Result<(), AuthError> { ) -> Result<(), AuthError> {
@ -2297,7 +2311,10 @@ impl AuthorizationManager {
return Ok(()); return Ok(());
}; };
let Some(received_issuer) = metadata.issuer.as_deref() else { let Some(received_issuer) = metadata.issuer.as_deref() else {
return Err(AuthError::AuthorizationServerMissingIssuer { expected_issuer }); if self.strict_issuer_validation {
return Err(AuthError::AuthorizationServerMissingIssuer { expected_issuer });
}
return Ok(());
}; };
if !Self::issuer_identifiers_match(received_issuer, &expected_issuer) { if !Self::issuer_identifiers_match(received_issuer, &expected_issuer) {
return Err(AuthError::AuthorizationServerMismatch { return Err(AuthError::AuthorizationServerMismatch {
@ -4006,8 +4023,8 @@ mod tests {
); );
} }
#[test] #[tokio::test]
fn authorization_metadata_accepts_oidc_path_appended_issuer() { async fn authorization_metadata_accepts_oidc_path_appended_issuer() {
let discovery_url = let discovery_url =
Url::parse("https://auth.example.com/tenant1/.well-known/openid-configuration") Url::parse("https://auth.example.com/tenant1/.well-known/openid-configuration")
.unwrap(); .unwrap();
@ -4017,8 +4034,12 @@ mod tests {
token_endpoint: "https://auth.example.com/tenant1/token".to_string(), token_endpoint: "https://auth.example.com/tenant1/token".to_string(),
..Default::default() ..Default::default()
}; };
let manager = AuthorizationManager::new("https://mcp.example.com/")
.await
.unwrap();
AuthorizationManager::validate_authorization_metadata_issuer(&discovery_url, &metadata) manager
.validate_authorization_metadata_issuer(&discovery_url, &metadata)
.unwrap(); .unwrap();
} }
@ -4034,8 +4055,8 @@ mod tests {
)); ));
} }
#[test] #[tokio::test]
fn authorization_metadata_accepts_oidc_path_inserted_issuer() { async fn authorization_metadata_accepts_oidc_path_inserted_issuer() {
let discovery_url = let discovery_url =
Url::parse("https://auth.example.com/.well-known/openid-configuration/tenant1") Url::parse("https://auth.example.com/.well-known/openid-configuration/tenant1")
.unwrap(); .unwrap();
@ -4045,13 +4066,17 @@ mod tests {
token_endpoint: "https://auth.example.com/tenant1/token".to_string(), token_endpoint: "https://auth.example.com/tenant1/token".to_string(),
..Default::default() ..Default::default()
}; };
let manager = AuthorizationManager::new("https://mcp.example.com/")
.await
.unwrap();
AuthorizationManager::validate_authorization_metadata_issuer(&discovery_url, &metadata) manager
.validate_authorization_metadata_issuer(&discovery_url, &metadata)
.unwrap(); .unwrap();
} }
#[test] #[tokio::test]
fn authorization_metadata_rejects_missing_issuer_for_standard_discovery_url() { async fn authorization_metadata_allows_missing_issuer_by_default() {
let discovery_url = let discovery_url =
Url::parse("https://auth.example.com/.well-known/openid-configuration/tenant1") Url::parse("https://auth.example.com/.well-known/openid-configuration/tenant1")
.unwrap(); .unwrap();
@ -4062,9 +4087,34 @@ mod tests {
..Default::default() ..Default::default()
}; };
let error = let manager = AuthorizationManager::new("https://mcp.example.com/")
AuthorizationManager::validate_authorization_metadata_issuer(&discovery_url, &metadata) .await
.unwrap_err(); .unwrap();
manager
.validate_authorization_metadata_issuer(&discovery_url, &metadata)
.unwrap();
}
#[tokio::test]
async fn authorization_metadata_rejects_missing_issuer_when_required() {
let discovery_url =
Url::parse("https://auth.example.com/.well-known/openid-configuration/tenant1")
.unwrap();
let metadata = AuthorizationMetadata {
issuer: None,
authorization_endpoint: "https://auth.example.com/tenant1/authorize".to_string(),
token_endpoint: "https://auth.example.com/tenant1/token".to_string(),
..Default::default()
};
let mut manager = AuthorizationManager::new("https://mcp.example.com/")
.await
.unwrap();
manager.set_strict_issuer_validation(true);
let error = manager
.validate_authorization_metadata_issuer(&discovery_url, &metadata)
.unwrap_err();
assert!( assert!(
matches!( matches!(