Skip to content

Commit f6ebc7a

Browse files
authored
fix(auth): oauth metadata discovery (#641)
* fix(auth): oauth metadata discovery * fix: format auth.rs
1 parent bfd9cc0 commit f6ebc7a

File tree

1 file changed

+36
-3
lines changed

1 file changed

+36
-3
lines changed

crates/rmcp/src/transport/auth.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,8 @@ impl AuthorizationManager {
807807
push_candidate(format!("/.well-known/openid-configuration/{trimmed}"));
808808
// 3. OpenID Connect with path appending
809809
push_candidate(format!("/{trimmed}/.well-known/openid-configuration"));
810+
// 4. Canonical OAuth fallback (without path suffix)
811+
push_candidate("/.well-known/oauth-authorization-server".to_string());
810812
}
811813

812814
candidates
@@ -1605,7 +1607,7 @@ mod tests {
16051607
// Test URL with single path segment: follow spec priority order
16061608
let base_url = Url::parse("https://auth.example.com/tenant1").unwrap();
16071609
let urls = AuthorizationManager::generate_discovery_urls(&base_url);
1608-
assert_eq!(urls.len(), 3);
1610+
assert_eq!(urls.len(), 4);
16091611
assert_eq!(
16101612
urls[0].as_str(),
16111613
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1"
@@ -1618,11 +1620,15 @@ mod tests {
16181620
urls[2].as_str(),
16191621
"https://auth.example.com/tenant1/.well-known/openid-configuration"
16201622
);
1623+
assert_eq!(
1624+
urls[3].as_str(),
1625+
"https://auth.example.com/.well-known/oauth-authorization-server"
1626+
);
16211627

16221628
// Test URL with path and trailing slash
16231629
let base_url = Url::parse("https://auth.example.com/v1/mcp/").unwrap();
16241630
let urls = AuthorizationManager::generate_discovery_urls(&base_url);
1625-
assert_eq!(urls.len(), 3);
1631+
assert_eq!(urls.len(), 4);
16261632
assert_eq!(
16271633
urls[0].as_str(),
16281634
"https://auth.example.com/.well-known/oauth-authorization-server/v1/mcp"
@@ -1635,11 +1641,15 @@ mod tests {
16351641
urls[2].as_str(),
16361642
"https://auth.example.com/v1/mcp/.well-known/openid-configuration"
16371643
);
1644+
assert_eq!(
1645+
urls[3].as_str(),
1646+
"https://auth.example.com/.well-known/oauth-authorization-server"
1647+
);
16381648

16391649
// Test URL with multiple path segments
16401650
let base_url = Url::parse("https://auth.example.com/tenant1/subtenant").unwrap();
16411651
let urls = AuthorizationManager::generate_discovery_urls(&base_url);
1642-
assert_eq!(urls.len(), 3);
1652+
assert_eq!(urls.len(), 4);
16431653
assert_eq!(
16441654
urls[0].as_str(),
16451655
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1/subtenant"
@@ -1652,6 +1662,10 @@ mod tests {
16521662
urls[2].as_str(),
16531663
"https://auth.example.com/tenant1/subtenant/.well-known/openid-configuration"
16541664
);
1665+
assert_eq!(
1666+
urls[3].as_str(),
1667+
"https://auth.example.com/.well-known/oauth-authorization-server"
1668+
);
16551669
}
16561670

16571671
// StateStore and StoredAuthorizationState tests
@@ -1786,6 +1800,25 @@ mod tests {
17861800
}
17871801
}
17881802

1803+
#[test]
1804+
fn test_discovery_urls_with_path_suffix() {
1805+
// When the base URL has a path suffix (e.g., /mcp), the discovery should
1806+
// eventually fall back to checking /.well-known/oauth-authorization-server
1807+
// at the root, not just /.well-known/oauth-authorization-server/{path}.
1808+
let base_url = Url::parse("https://mcp.example.com/mcp").unwrap();
1809+
let urls = AuthorizationManager::generate_discovery_urls(&base_url);
1810+
1811+
let canonical_oauth_fallback =
1812+
"https://mcp.example.com/.well-known/oauth-authorization-server";
1813+
1814+
assert!(
1815+
urls.iter().any(|u| u.as_str() == canonical_oauth_fallback),
1816+
"Expected discovery URLs to include canonical OAuth fallback '{}', but got: {:?}",
1817+
canonical_oauth_fallback,
1818+
urls.iter().map(|u| u.as_str()).collect::<Vec<_>>()
1819+
);
1820+
}
1821+
17891822
#[tokio::test]
17901823
async fn test_custom_state_store_with_authorization_manager() {
17911824
use std::sync::atomic::{AtomicUsize, Ordering};

0 commit comments

Comments
 (0)