Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions crates/rmcp/src/transport/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,15 @@ 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,
pub registration_endpoint: Option<String>,
pub issuer: Option<String>,
pub jwks_uri: Option<String>,
pub scopes_supported: Option<Vec<String>>,
pub response_types_supported: Option<Vec<String>>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the option here be removed for compatibility with rfc8414?

// allow additional fields
#[serde(flatten)]
pub additional_fields: HashMap<String, serde_json::Value>,
Expand Down Expand Up @@ -290,11 +291,7 @@ impl AuthorizationManager {
Ok(AuthorizationMetadata {
authorization_endpoint: create_endpoint("authorize"),
token_endpoint: create_endpoint("token"),
registration_endpoint: None,
issuer: None,
jwks_uri: None,
scopes_supported: None,
additional_fields: HashMap::new(),
..Default::default()
})
}

Expand Down Expand Up @@ -339,7 +336,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,
Expand All @@ -357,6 +364,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(),
Expand Down Expand Up @@ -446,6 +457,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();

Expand Down
1 change: 1 addition & 0 deletions examples/servers/src/complex_auth_sse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down