Skip to content

Commit b8b6001

Browse files
committed
fix(auth): match complete WWW-Authenticate params
1 parent e942d00 commit b8b6001

2 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/mcp/client/auth/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ def extract_field_from_www_auth(response: Response, field_name: str) -> str | No
2626
if not www_auth_header:
2727
return None
2828

29-
# Pattern matches: field_name="value" or field_name=value (unquoted)
30-
pattern = rf'{field_name}=(?:"([^"]+)"|([^\s,]+))'
29+
# Pattern matches a complete auth-param name, not a suffix of another
30+
# parameter such as error_scope or x_resource_metadata.
31+
pattern = rf'(?:^|[\s,]){re.escape(field_name)}=(?:"([^"]+)"|([^\s,]+))'
3132
match = re.search(pattern, www_auth_header)
3233

3334
if match:

tests/client/test_auth.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,54 @@ def test_extract_field_from_www_auth_invalid_cases(
20702070
result = extract_field_from_www_auth(init_response, field_name)
20712071
assert result is None, f"Should return None for {description}"
20722072

2073+
def test_extract_field_from_www_auth_does_not_match_substring_param_name(
2074+
self,
2075+
client_metadata: OAuthClientMetadata,
2076+
mock_storage: MockTokenStorage,
2077+
):
2078+
"""Test auth-param names are matched exactly, not as substrings."""
2079+
2080+
init_response = httpx.Response(
2081+
status_code=401,
2082+
headers={"WWW-Authenticate": 'Bearer error_scope="decoy", scope="read write"'},
2083+
request=httpx.Request("GET", "https://api.example.com/test"),
2084+
)
2085+
2086+
result = extract_field_from_www_auth(init_response, "scope")
2087+
assert result == "read write"
2088+
2089+
def test_extract_field_from_www_auth_ignores_prefixed_param_only(
2090+
self,
2091+
client_metadata: OAuthClientMetadata,
2092+
mock_storage: MockTokenStorage,
2093+
):
2094+
"""Test a prefixed auth-param does not satisfy the requested field."""
2095+
2096+
init_response = httpx.Response(
2097+
status_code=401,
2098+
headers={"WWW-Authenticate": 'Bearer custom_scope="leaked"'},
2099+
request=httpx.Request("GET", "https://api.example.com/test"),
2100+
)
2101+
2102+
result = extract_field_from_www_auth(init_response, "scope")
2103+
assert result is None
2104+
2105+
def test_extract_resource_metadata_from_www_auth_ignores_prefixed_param(
2106+
self,
2107+
client_metadata: OAuthClientMetadata,
2108+
mock_storage: MockTokenStorage,
2109+
):
2110+
"""Test resource_metadata does not match inside another auth-param name."""
2111+
2112+
init_response = httpx.Response(
2113+
status_code=401,
2114+
headers={"WWW-Authenticate": 'Bearer x_resource_metadata="https://decoy.example.com"'},
2115+
request=httpx.Request("GET", "https://api.example.com/test"),
2116+
)
2117+
2118+
result = extract_resource_metadata_from_www_auth(init_response)
2119+
assert result is None
2120+
20732121

20742122
class TestCIMD:
20752123
"""Test Client ID Metadata Document (CIMD) support."""

0 commit comments

Comments
 (0)