@@ -179,6 +179,33 @@ struct AuthorizationState {
179
179
}
180
180
181
181
impl AuthorizationManager {
182
+ fn well_known_paths ( base_path : & str , resource : & str ) -> Vec < String > {
183
+ let trimmed = base_path. trim_start_matches ( '/' ) . trim_end_matches ( '/' ) ;
184
+ let mut candidates = Vec :: new ( ) ;
185
+
186
+ let mut push_candidate = |candidate : String | {
187
+ if !candidates. contains ( & candidate) {
188
+ candidates. push ( candidate) ;
189
+ }
190
+ } ;
191
+
192
+ let canonical = format ! ( "/.well-known/{resource}" ) ;
193
+
194
+ if trimmed. is_empty ( ) {
195
+ push_candidate ( canonical) ;
196
+ return candidates;
197
+ }
198
+
199
+ // This follows the RFC 8414 recommendation for well-known URI discovery
200
+ push_candidate ( format ! ( "{canonical}/{trimmed}" ) ) ;
201
+ // This is a common pattern used by some identity providers
202
+ push_candidate ( format ! ( "/{trimmed}/.well-known/{resource}" ) ) ;
203
+ // The canonical path should always be the last fallback
204
+ push_candidate ( canonical) ;
205
+
206
+ candidates
207
+ }
208
+
182
209
/// create new auth manager with base url
183
210
pub async fn new < U : IntoUrl > ( base_url : U ) -> Result < Self , AuthError > {
184
211
let base_url = base_url. into_url ( ) ?;
@@ -207,53 +234,53 @@ impl AuthorizationManager {
207
234
208
235
/// discover oauth2 metadata
209
236
pub async fn discover_metadata ( & self ) -> Result < AuthorizationMetadata , AuthError > {
210
- // according to the specification, the metadata should be located at "/.well-known/oauth-authorization-server"
211
- let mut discovery_url = self . base_url . clone ( ) ;
212
- let path = discovery_url. path ( ) ;
213
- let path_suffix = if path == "/" { "" } else { path } ;
214
- discovery_url. set_path ( & format ! (
215
- "/.well-known/oauth-authorization-server{path_suffix}"
216
- ) ) ;
217
- debug ! ( "discovery url: {:?}" , discovery_url) ;
218
- let response = self
219
- . http_client
220
- . get ( discovery_url)
221
- . header ( "MCP-Protocol-Version" , "2024-11-05" )
222
- . send ( )
223
- . await ?;
224
-
225
- if response. status ( ) == StatusCode :: OK {
226
- let metadata = response
227
- . json :: < AuthorizationMetadata > ( )
228
- . await
229
- . map_err ( |e| {
230
- AuthError :: MetadataError ( format ! ( "Failed to parse metadata: {}" , e) )
231
- } ) ?;
232
- debug ! ( "metadata: {:?}" , metadata) ;
233
- Ok ( metadata)
234
- } else {
235
- // fallback to default endpoints
236
- let mut auth_base = self . base_url . clone ( ) ;
237
- // discard the path part, only keep scheme, host, port
238
- auth_base. set_path ( "" ) ;
239
-
240
- // Helper function to create endpoint URL
241
- let create_endpoint = |path : & str | -> String {
242
- let mut url = auth_base. clone ( ) ;
243
- url. set_path ( path) ;
244
- url. to_string ( )
245
- } ;
246
-
247
- Ok ( AuthorizationMetadata {
248
- authorization_endpoint : create_endpoint ( "authorize" ) ,
249
- token_endpoint : create_endpoint ( "token" ) ,
250
- registration_endpoint : create_endpoint ( "register" ) ,
251
- issuer : None ,
252
- jwks_uri : None ,
253
- scopes_supported : None ,
254
- additional_fields : HashMap :: new ( ) ,
255
- } )
237
+ for candidate_path in Self :: well_known_paths ( self . base_url . path ( ) , "oauth-authorization-server" ) {
238
+ let mut discovery_url = self . base_url . clone ( ) ;
239
+ discovery_url. set_path ( & candidate_path) ;
240
+ debug ! ( "discovery url: {:?}" , discovery_url) ;
241
+
242
+ let response = self
243
+ . http_client
244
+ . get ( discovery_url)
245
+ . header ( "MCP-Protocol-Version" , "2024-11-05" )
246
+ . send ( )
247
+ . await ?;
248
+
249
+ if response. status ( ) == StatusCode :: OK {
250
+ let metadata = response
251
+ . json :: < AuthorizationMetadata > ( )
252
+ . await
253
+ . map_err ( |e| {
254
+ AuthError :: MetadataError ( format ! ( "Failed to parse metadata: {}" , e) )
255
+ } ) ?;
256
+ debug ! ( "metadata: {:?}" , metadata) ;
257
+ return Ok ( metadata) ;
258
+ }
256
259
}
260
+
261
+ debug ! ( "No valid metadata found, falling back to default endpoints" ) ;
262
+
263
+ // fallback to default endpoints
264
+ let mut auth_base = self . base_url . clone ( ) ;
265
+ // discard the path part, only keep scheme, host, port
266
+ auth_base. set_path ( "" ) ;
267
+
268
+ // Helper function to create endpoint URL
269
+ let create_endpoint = |path : & str | -> String {
270
+ let mut url = auth_base. clone ( ) ;
271
+ url. set_path ( path) ;
272
+ url. to_string ( )
273
+ } ;
274
+
275
+ Ok ( AuthorizationMetadata {
276
+ authorization_endpoint : create_endpoint ( "authorize" ) ,
277
+ token_endpoint : create_endpoint ( "token" ) ,
278
+ registration_endpoint : create_endpoint ( "register" ) ,
279
+ issuer : None ,
280
+ jwks_uri : None ,
281
+ scopes_supported : None ,
282
+ additional_fields : HashMap :: new ( ) ,
283
+ } )
257
284
}
258
285
259
286
/// get client id and credentials
@@ -876,3 +903,40 @@ impl OAuthState {
876
903
}
877
904
}
878
905
}
906
+
907
+ #[ cfg( test) ]
908
+ mod tests {
909
+ use super :: AuthorizationManager ;
910
+
911
+ #[ test]
912
+ fn well_known_paths_root ( ) {
913
+ let paths = AuthorizationManager :: well_known_paths ( "/" , "oauth-authorization-server" ) ;
914
+ assert_eq ! ( paths, vec![ "/.well-known/oauth-authorization-server" . to_string( ) ] ) ;
915
+ }
916
+
917
+ #[ test]
918
+ fn well_known_paths_with_suffix ( ) {
919
+ let paths = AuthorizationManager :: well_known_paths ( "/mcp" , "oauth-authorization-server" ) ;
920
+ assert_eq ! (
921
+ paths,
922
+ vec![
923
+ "/.well-known/oauth-authorization-server/mcp" . to_string( ) ,
924
+ "/mcp/.well-known/oauth-authorization-server" . to_string( ) ,
925
+ "/.well-known/oauth-authorization-server" . to_string( ) ,
926
+ ]
927
+ ) ;
928
+ }
929
+
930
+ #[ test]
931
+ fn well_known_paths_trailing_slash ( ) {
932
+ let paths = AuthorizationManager :: well_known_paths ( "/mcp/v1/" , "oauth-authorization-server" ) ;
933
+ assert_eq ! (
934
+ paths,
935
+ vec![
936
+ "/.well-known/oauth-authorization-server/mcp/v1" . to_string( ) ,
937
+ "/mcp/v1/.well-known/oauth-authorization-server" . to_string( ) ,
938
+ "/.well-known/oauth-authorization-server" . to_string( ) ,
939
+ ]
940
+ ) ;
941
+ }
942
+ }
0 commit comments