diff --git a/crates/rmcp/src/transport/auth.rs b/crates/rmcp/src/transport/auth.rs index 0cb8bec8..1f1438d6 100644 --- a/crates/rmcp/src/transport/auth.rs +++ b/crates/rmcp/src/transport/auth.rs @@ -101,7 +101,7 @@ pub enum AuthError { } /// oauth2 metadata -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct AuthorizationMetadata { pub authorization_endpoint: String, pub token_endpoint: String, @@ -109,6 +109,7 @@ pub struct AuthorizationMetadata { pub issuer: Option, pub jwks_uri: Option, pub scopes_supported: Option>, + pub response_types_supported: Option>, // allow additional fields #[serde(flatten)] pub additional_fields: HashMap, @@ -297,7 +298,17 @@ impl AuthorizationManager { self.oauth_client = Some(client_builder); Ok(()) } - + /// validate if the server support the response type + fn validate_response_supported(&self, response_type: &str) -> Result<(), AuthError> { + if let Some(metadata) = self.metadata.as_ref() { + if let Some(response_types_supported) = metadata.response_types_supported.as_ref() { + if !response_types_supported.contains(&response_type.to_string()) { + return Err(AuthError::InvalidScope(response_type.to_string())); + } + } + } + Ok(()) + } /// dynamic register oauth2 client pub async fn register_client( &mut self, @@ -315,6 +326,10 @@ impl AuthorizationManager { )); }; + // RFC 8414 RECOMMENDS response_types_supported in the metadata. This field is optional, + // but if present and does not include the flow we use ("code"), bail out early with a clear error. + self.validate_response_supported("code")?; + // prepare registration request let registration_request = ClientRegistrationRequest { client_name: name.to_string(), @@ -404,6 +419,9 @@ impl AuthorizationManager { .as_ref() .ok_or_else(|| AuthError::InternalError("OAuth client not configured".to_string()))?; + // ensure the server supports the response type we intend to use when metadata is available + self.validate_response_supported("code")?; + // generate pkce challenge let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); diff --git a/examples/servers/src/complex_auth_sse.rs b/examples/servers/src/complex_auth_sse.rs index c3f9fc36..5a549a39 100644 --- a/examples/servers/src/complex_auth_sse.rs +++ b/examples/servers/src/complex_auth_sse.rs @@ -526,6 +526,7 @@ async fn oauth_authorization_server() -> impl IntoResponse { token_endpoint: format!("http://{}/oauth/token", BIND_ADDRESS), scopes_supported: Some(vec!["profile".to_string(), "email".to_string()]), registration_endpoint: Some(format!("http://{}/oauth/register", BIND_ADDRESS)), + response_types_supported: Some(vec!["code".to_string()]), issuer: Some(BIND_ADDRESS.to_string()), jwks_uri: Some(format!("http://{}/oauth/jwks", BIND_ADDRESS)), additional_fields,