From 2b26c15253e1c7c2ae4bd8c25104703515a09738 Mon Sep 17 00:00:00 2001 From: Akshita <110122283+akshita317@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:04:01 +0530 Subject: [PATCH] oauthex: accept an empty quoted-string auth-param value parseSingleChallenge rejected any auth-param whose value was empty, including a quoted-string such as realm="". RFC 9110, section 5.6.4, allows a quoted-string to be empty; only a token must be non-empty. Because ParseWWWAuthenticate fails the whole header on one bad challenge, a server that sent realm="" or error_description="" made AuthorizationCodeHandler.Authorize and the client-credentials handler return "failed to parse WWW-Authenticate header", so the client never reached the resource_metadata parameter and could not authorize. Only reject an empty value when it is unquoted. Co-Authored-By: Claude Opus 5.5 --- oauthex/resource_meta.go | 8 +++++--- oauthex/resource_meta_test.go | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/oauthex/resource_meta.go b/oauthex/resource_meta.go index 078b2f33..31d6b37c 100644 --- a/oauthex/resource_meta.go +++ b/oauthex/resource_meta.go @@ -289,9 +289,11 @@ func parseSingleChallenge(s string) (Challenge, error) { value = strings.TrimSpace(paramsStr[:commaPos]) paramsStr = strings.TrimSpace(paramsStr[commaPos:]) // Keep comma for next check } - } - if value == "" { - return Challenge{}, fmt.Errorf("no value for auth param %q", key) + // A token is one or more characters. A quoted string may be empty + // (RFC 9110, section 5.6.4), so only an unquoted value is checked here. + if value == "" { + return Challenge{}, fmt.Errorf("no value for auth param %q", key) + } } // Per RFC 9110, parameter keys are case-insensitive. diff --git a/oauthex/resource_meta_test.go b/oauthex/resource_meta_test.go index e056a64e..dcac6740 100644 --- a/oauthex/resource_meta_test.go +++ b/oauthex/resource_meta_test.go @@ -95,6 +95,28 @@ func TestParseWWWAuthenticateEscapedBackslash(t *testing.T) { } } +// A quoted string may be empty. An empty realm or error_description must not +// make the whole header unparseable, or the auth handlers never see the +// resource_metadata parameter that follows it. +func TestParseWWWAuthenticateEmptyQuotedValue(t *testing.T) { + got, err := ParseWWWAuthenticate([]string{ + `Bearer realm="", error_description="", resource_metadata="https://example.com/.well-known/oauth-protected-resource"`, + }) + if err != nil { + t.Fatal(err) + } + want := []Challenge{ + {Scheme: "bearer", Params: map[string]string{ + "realm": "", + "error_description": "", + "resource_metadata": "https://example.com/.well-known/oauth-protected-resource", + }}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("ParseWWWAuthenticate() = %+v, want %+v", got, want) + } +} + func TestSplitChallengesError(t *testing.T) { if _, err := splitChallenges(`"Bearer"`); err == nil { t.Fatal("got nil, want error") @@ -192,6 +214,20 @@ func TestParseSingleChallenge(t *testing.T) { input: "Bearer realm=", wantErr: true, }, + { + name: "empty quoted param", + input: `Bearer realm="", error="invalid_token"`, + want: Challenge{ + Scheme: "bearer", + Params: map[string]string{"realm": "", "error": "invalid_token"}, + }, + wantErr: false, + }, + { + name: "malformed param - no value before comma", + input: `Bearer realm=, error="invalid_token"`, + wantErr: true, + }, { name: "malformed param - unterminated quote", input: `Bearer realm="example`,