Skip to content

Commit 95490fa

Browse files
authored
feat: reject auth servers lacking S256 PKCE support (#955)
1 parent bdf0c32 commit 95490fa

1 file changed

Lines changed: 46 additions & 16 deletions

File tree

crates/rmcp/src/transport/auth.rs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,9 @@ pub enum AuthError {
477477
#[error("Metadata error: {0}")]
478478
MetadataError(String),
479479

480+
#[error("Authorization server does not support the required PKCE code challenge method (S256)")]
481+
PkceUnsupported,
482+
480483
#[error("URL parse error: {0}")]
481484
UrlError(#[from] url::ParseError),
482485

@@ -1159,18 +1162,21 @@ impl AuthorizationManager {
11591162
}
11601163
}
11611164

1162-
// for PKCE, we always send s256 since oauth 2.1 requires servers to support it,
1163-
// but warn if the server metadata suggests otherwise
1165+
// The client always sends an S256 challenge. A server that advertises
1166+
// methods without S256 can't do the flow we require, so refuse it. A
1167+
// server that omits the field is tolerated: it usually means the server
1168+
// didn't advertise PKCE, not that it lacks S256.
11641169
match &metadata.code_challenge_methods_supported {
11651170
Some(methods) if !methods.iter().any(|m| m == "S256") => {
1171+
return Err(AuthError::PkceUnsupported);
1172+
}
1173+
None => {
11661174
warn!(
1167-
?methods,
1168-
"server does not advertise S256 in code_challenge_methods_supported, \
1169-
proceeding with S256 anyway as oauth 2.1 requires it. \
1170-
The server is not compliant with the specification!"
1175+
"authorization server metadata omits code_challenge_methods_supported; \
1176+
proceeding with an S256 challenge anyway"
11711177
);
11721178
}
1173-
_ => {}
1179+
Some(_) => {}
11741180
}
11751181

11761182
Ok(())
@@ -4307,19 +4313,43 @@ mod tests {
43074313
assert!(manager.validate_server_metadata("code").is_err());
43084314
}
43094315

4310-
#[tokio::test]
4311-
async fn test_validate_as_metadata_passes_without_pkce_s256() {
4312-
let mut manager = AuthorizationManager::new("https://example.com")
4313-
.await
4314-
.unwrap();
4315-
let metadata = AuthorizationMetadata {
4316+
fn as_metadata_with_pkce(methods: Option<Vec<String>>) -> AuthorizationMetadata {
4317+
AuthorizationMetadata {
43164318
authorization_endpoint: "https://auth.example.com/authorize".to_string(),
43174319
token_endpoint: "https://auth.example.com/token".to_string(),
43184320
response_types_supported: Some(vec!["code".to_string()]),
4319-
code_challenge_methods_supported: Some(vec!["plain".to_string()]),
4321+
code_challenge_methods_supported: methods,
43204322
..Default::default()
4321-
};
4322-
manager.set_metadata(metadata);
4323+
}
4324+
}
4325+
4326+
#[tokio::test]
4327+
async fn test_validate_as_metadata_rejects_without_pkce_s256() {
4328+
let mut manager = AuthorizationManager::new("https://example.com")
4329+
.await
4330+
.unwrap();
4331+
manager.set_metadata(as_metadata_with_pkce(Some(vec!["plain".to_string()])));
4332+
assert!(matches!(
4333+
manager.validate_server_metadata("code"),
4334+
Err(AuthError::PkceUnsupported)
4335+
));
4336+
}
4337+
4338+
#[tokio::test]
4339+
async fn test_validate_as_metadata_allows_absent_pkce_methods_by_default() {
4340+
let mut manager = AuthorizationManager::new("https://example.com")
4341+
.await
4342+
.unwrap();
4343+
manager.set_metadata(as_metadata_with_pkce(None));
4344+
assert!(manager.validate_server_metadata("code").is_ok());
4345+
}
4346+
4347+
#[tokio::test]
4348+
async fn test_validate_as_metadata_passes_with_pkce_s256() {
4349+
let mut manager = AuthorizationManager::new("https://example.com")
4350+
.await
4351+
.unwrap();
4352+
manager.set_metadata(as_metadata_with_pkce(Some(vec!["S256".to_string()])));
43234353
assert!(manager.validate_server_metadata("code").is_ok());
43244354
}
43254355

0 commit comments

Comments
 (0)