@@ -199,3 +199,155 @@ func TestTracePropagationHeaders(t *testing.T) {
199199 // Verify the request still works normally
200200 assert .Equal (t , http .StatusOK , recorder .Code )
201201}
202+
203+ func TestWellKnownPathPrefixMatching (t * testing.T ) {
204+ t .Parallel ()
205+
206+ tests := []struct {
207+ name string
208+ requestPath string
209+ expectedStatusCode int
210+ shouldCallHandler bool
211+ description string
212+ }{
213+ {
214+ name : "base path without resource component" ,
215+ requestPath : "/.well-known/oauth-protected-resource" ,
216+ expectedStatusCode : http .StatusOK ,
217+ shouldCallHandler : true ,
218+ description : "RFC 9728 base path should route to authInfoHandler" ,
219+ },
220+ {
221+ name : "path with single resource component" ,
222+ requestPath : "/.well-known/oauth-protected-resource/mcp" ,
223+ expectedStatusCode : http .StatusOK ,
224+ shouldCallHandler : true ,
225+ description : "Path with /mcp resource component should route to authInfoHandler" ,
226+ },
227+ {
228+ name : "path with multiple resource components" ,
229+ requestPath : "/.well-known/oauth-protected-resource/api/v1/service" ,
230+ expectedStatusCode : http .StatusOK ,
231+ shouldCallHandler : true ,
232+ description : "Path with multiple resource components should route to authInfoHandler" ,
233+ },
234+ {
235+ name : "path with different resource name" ,
236+ requestPath : "/.well-known/oauth-protected-resource/resource1" ,
237+ expectedStatusCode : http .StatusOK ,
238+ shouldCallHandler : true ,
239+ description : "Path with arbitrary resource component should route to authInfoHandler" ,
240+ },
241+ {
242+ name : "non-matching well-known path" ,
243+ requestPath : "/.well-known/other-endpoint" ,
244+ expectedStatusCode : http .StatusNotFound ,
245+ shouldCallHandler : false ,
246+ description : "Different well-known endpoint should return 404" ,
247+ },
248+ {
249+ name : "path without leading dot" ,
250+ requestPath : "/well-known/oauth-protected-resource" ,
251+ expectedStatusCode : http .StatusNotFound ,
252+ shouldCallHandler : false ,
253+ description : "Path without leading dot should return 404" ,
254+ },
255+ {
256+ name : "similar but non-matching path with suffix" ,
257+ requestPath : "/.well-known/oauth-protected-resource-other" ,
258+ expectedStatusCode : http .StatusOK ,
259+ shouldCallHandler : true ,
260+ description : "Per RFC 9728, prefix matching means this should match" ,
261+ },
262+ {
263+ name : "path with trailing slash" ,
264+ requestPath : "/.well-known/oauth-protected-resource/" ,
265+ expectedStatusCode : http .StatusOK ,
266+ shouldCallHandler : true ,
267+ description : "Path with trailing slash should route to authInfoHandler" ,
268+ },
269+ {
270+ name : "path with query parameters" ,
271+ requestPath : "/.well-known/oauth-protected-resource?param=value" ,
272+ expectedStatusCode : http .StatusOK ,
273+ shouldCallHandler : true ,
274+ description : "Path with query parameters should route to authInfoHandler" ,
275+ },
276+ }
277+
278+ for _ , tt := range tests {
279+ tt := tt // capture range variable
280+ t .Run (tt .name , func (t * testing.T ) {
281+ t .Parallel ()
282+
283+ // Track whether the auth info handler was called
284+ handlerCalled := false
285+ authHandler := http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
286+ handlerCalled = true
287+ w .WriteHeader (http .StatusOK )
288+ w .Write ([]byte (`{"authorized": true}` ))
289+ })
290+
291+ // Create the well-known handler directly (same logic as in transparent_proxy.go)
292+ wellKnownHandler := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
293+ // Per RFC 9728, match /.well-known/oauth-protected-resource and any subpaths
294+ // e.g., /.well-known/oauth-protected-resource/mcp
295+ if strings .HasPrefix (r .URL .Path , "/.well-known/oauth-protected-resource" ) {
296+ authHandler .ServeHTTP (w , r )
297+ } else {
298+ http .NotFound (w , r )
299+ }
300+ })
301+
302+ // Create a mux and register the well-known handler (same as in transparent_proxy.go)
303+ mux := http .NewServeMux ()
304+ mux .Handle ("/.well-known/" , wellKnownHandler )
305+
306+ // Create a test request
307+ req := httptest .NewRequest ("GET" , tt .requestPath , nil )
308+ recorder := httptest .NewRecorder ()
309+
310+ // Serve the request through the mux
311+ mux .ServeHTTP (recorder , req )
312+
313+ // Verify status code
314+ assert .Equal (t , tt .expectedStatusCode , recorder .Code ,
315+ "%s: expected status %d but got %d" , tt .description , tt .expectedStatusCode , recorder .Code )
316+
317+ // Verify whether handler was called
318+ assert .Equal (t , tt .shouldCallHandler , handlerCalled ,
319+ "%s: handler call mismatch (expected=%v, actual=%v)" , tt .description , tt .shouldCallHandler , handlerCalled )
320+
321+ // For successful cases, verify response body
322+ if tt .shouldCallHandler && recorder .Code == http .StatusOK {
323+ assert .Contains (t , recorder .Body .String (), "authorized" ,
324+ "%s: expected response body to contain auth info" , tt .description )
325+ }
326+ })
327+ }
328+ }
329+
330+ func TestWellKnownPathWithoutAuthHandler (t * testing.T ) {
331+ t .Parallel ()
332+
333+ // Test that when authInfoHandler is nil, the well-known route is not registered
334+ // Create a mux without registering the well-known handler (simulating authInfoHandler == nil case)
335+ mux := http .NewServeMux ()
336+
337+ // Only register a default handler that returns 404 for everything
338+ mux .HandleFunc ("/" , func (w http.ResponseWriter , r * http.Request ) {
339+ http .NotFound (w , r )
340+ })
341+
342+ // Create a test request to well-known path
343+ req := httptest .NewRequest ("GET" , "/.well-known/oauth-protected-resource" , nil )
344+ recorder := httptest .NewRecorder ()
345+
346+ // Serve the request
347+ mux .ServeHTTP (recorder , req )
348+
349+ // When no auth handler is provided, the well-known route should not be registered
350+ // The request should fall through to the default handler which returns 404
351+ assert .Equal (t , http .StatusNotFound , recorder .Code ,
352+ "Without auth handler, well-known path should return 404" )
353+ }
0 commit comments