From 37153e430ced03542e83f7f6be7cf1343a61c284 Mon Sep 17 00:00:00 2001 From: jay23606 <4602056+jay23606@users.noreply.github.com> Date: Mon, 12 Oct 2026 17:54:25 -0400 Subject: [PATCH 1/2] auth: use bearer resource metadata challenges Fixes #1379 --- auth/authorization_code.go | 2 +- auth/authorization_code_test.go | 10 ++++++++++ auth/extauth/client_credentials.go | 2 +- auth/extauth/client_credentials_test.go | 10 ++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/auth/authorization_code.go b/auth/authorization_code.go index 142bac12f..e33f96e8c 100644 --- a/auth/authorization_code.go +++ b/auth/authorization_code.go @@ -405,7 +405,7 @@ func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Requ // or the empty string if there is none. func resourceMetadataURLFromChallenges(cs []oauthex.Challenge) string { for _, c := range cs { - if u := c.Params["resource_metadata"]; u != "" { + if u := c.Params["resource_metadata"]; strings.EqualFold(c.Scheme, "Bearer") && u != "" { return u } } diff --git a/auth/authorization_code_test.go b/auth/authorization_code_test.go index 862514627..d9b9784a5 100644 --- a/auth/authorization_code_test.go +++ b/auth/authorization_code_test.go @@ -1606,3 +1606,13 @@ func TestInitialTokenSource(t *testing.T) { t.Errorf("expected access token 'set_token', got '%s'", tok.AccessToken) } } + +func TestResourceMetadataURLFromChallengesUsesBearerChallenge(t *testing.T) { + challenges := []oauthex.Challenge{ + {Scheme: "Basic", Params: map[string]string{"resource_metadata": "https://basic.example/metadata"}}, + {Scheme: "Bearer", Params: map[string]string{"resource_metadata": "https://bearer.example/metadata"}}, + } + if got, want := resourceMetadataURLFromChallenges(challenges), "https://bearer.example/metadata"; got != want { + t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) + } +} diff --git a/auth/extauth/client_credentials.go b/auth/extauth/client_credentials.go index c65d8b066..35363b41e 100644 --- a/auth/extauth/client_credentials.go +++ b/auth/extauth/client_credentials.go @@ -239,7 +239,7 @@ func protectedResourceMetadataURLs(metadataURL, resourceURL string) []prmURL { // WWW-Authenticate challenges, or the empty string if there is none. func resourceMetadataURLFromChallenges(cs []oauthex.Challenge) string { for _, c := range cs { - if u := c.Params["resource_metadata"]; u != "" { + if u := c.Params["resource_metadata"]; strings.EqualFold(c.Scheme, "Bearer") && u != "" { return u } } diff --git a/auth/extauth/client_credentials_test.go b/auth/extauth/client_credentials_test.go index b2973de7c..2589404a4 100644 --- a/auth/extauth/client_credentials_test.go +++ b/auth/extauth/client_credentials_test.go @@ -418,3 +418,13 @@ func TestSelectTokenAuthMethod(t *testing.T) { }) } } + +func TestResourceMetadataURLFromChallengesUsesBearerChallenge(t *testing.T) { + challenges := []oauthex.Challenge{ + {Scheme: "Basic", Params: map[string]string{"resource_metadata": "https://basic.example/metadata"}}, + {Scheme: "Bearer", Params: map[string]string{"resource_metadata": "https://bearer.example/metadata"}}, + } + if got, want := resourceMetadataURLFromChallenges(challenges), "https://bearer.example/metadata"; got != want { + t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) + } +} From 32a7c84c9ebb5a5d132d391e152fdd833d14de09 Mon Sep 17 00:00:00 2001 From: jay23606 <4602056+jay23606@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:35:30 -0400 Subject: [PATCH 2/2] auth: fall back to another scheme's resource metadata challenge Restricting the resource_metadata lookup to a Bearer challenge dropped the URL for servers that only challenge with another scheme, such as Basic. Prefer a Bearer challenge and otherwise take the first other scheme that carries one, in both the auth and extauth packages. Co-Authored-By: Claude Haiku 5.5 --- auth/authorization_code.go | 15 ++++++++++++--- auth/authorization_code_test.go | 11 +++++++++++ auth/extauth/client_credentials.go | 16 +++++++++++++--- auth/extauth/client_credentials_test.go | 11 +++++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/auth/authorization_code.go b/auth/authorization_code.go index e33f96e8c..a49c100e2 100644 --- a/auth/authorization_code.go +++ b/auth/authorization_code.go @@ -402,14 +402,23 @@ func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Requ } // resourceMetadataURLFromChallenges returns a resource metadata URL from the given "WWW-Authenticate" header challenges, -// or the empty string if there is none. +// preferring a "Bearer" challenge and otherwise the first other scheme that carries one. It returns the empty string if +// there is none. func resourceMetadataURLFromChallenges(cs []oauthex.Challenge) string { + var fallback string for _, c := range cs { - if u := c.Params["resource_metadata"]; strings.EqualFold(c.Scheme, "Bearer") && u != "" { + u := c.Params["resource_metadata"] + if u == "" { + continue + } + if strings.EqualFold(c.Scheme, "Bearer") { return u } + if fallback == "" { + fallback = u + } } - return "" + return fallback } // scopesFromChallenges returns the scopes from the given "WWW-Authenticate" header challenges. diff --git a/auth/authorization_code_test.go b/auth/authorization_code_test.go index d9b9784a5..ee995e211 100644 --- a/auth/authorization_code_test.go +++ b/auth/authorization_code_test.go @@ -1616,3 +1616,14 @@ func TestResourceMetadataURLFromChallengesUsesBearerChallenge(t *testing.T) { t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) } } + +func TestResourceMetadataURLFromChallengesFallsBackToOtherSchemes(t *testing.T) { + challenges := []oauthex.Challenge{ + {Scheme: "Basic", Params: map[string]string{}}, + {Scheme: "Digest", Params: map[string]string{"resource_metadata": "https://digest.example/metadata"}}, + {Scheme: "Basic", Params: map[string]string{"resource_metadata": "https://basic.example/metadata"}}, + } + if got, want := resourceMetadataURLFromChallenges(challenges), "https://digest.example/metadata"; got != want { + t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) + } +} diff --git a/auth/extauth/client_credentials.go b/auth/extauth/client_credentials.go index 35363b41e..cdd99b8fb 100644 --- a/auth/extauth/client_credentials.go +++ b/auth/extauth/client_credentials.go @@ -236,14 +236,24 @@ func protectedResourceMetadataURLs(metadataURL, resourceURL string) []prmURL { } // resourceMetadataURLFromChallenges returns a resource metadata URL from -// WWW-Authenticate challenges, or the empty string if there is none. +// WWW-Authenticate challenges, preferring a Bearer challenge and otherwise the +// first other scheme that carries one. It returns the empty string if there is +// none. func resourceMetadataURLFromChallenges(cs []oauthex.Challenge) string { + var fallback string for _, c := range cs { - if u := c.Params["resource_metadata"]; strings.EqualFold(c.Scheme, "Bearer") && u != "" { + u := c.Params["resource_metadata"] + if u == "" { + continue + } + if strings.EqualFold(c.Scheme, "Bearer") { return u } + if fallback == "" { + fallback = u + } } - return "" + return fallback } // scopesFromChallenges returns scopes from WWW-Authenticate challenges. diff --git a/auth/extauth/client_credentials_test.go b/auth/extauth/client_credentials_test.go index 2589404a4..8c87bb537 100644 --- a/auth/extauth/client_credentials_test.go +++ b/auth/extauth/client_credentials_test.go @@ -428,3 +428,14 @@ func TestResourceMetadataURLFromChallengesUsesBearerChallenge(t *testing.T) { t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) } } + +func TestResourceMetadataURLFromChallengesFallsBackToOtherSchemes(t *testing.T) { + challenges := []oauthex.Challenge{ + {Scheme: "Basic", Params: map[string]string{}}, + {Scheme: "Digest", Params: map[string]string{"resource_metadata": "https://digest.example/metadata"}}, + {Scheme: "Basic", Params: map[string]string{"resource_metadata": "https://basic.example/metadata"}}, + } + if got, want := resourceMetadataURLFromChallenges(challenges), "https://digest.example/metadata"; got != want { + t.Errorf("resourceMetadataURLFromChallenges() = %q, want %q", got, want) + } +}