Skip to content

Commit 35fd692

Browse files
committed
fix(auth): satisfy pyright + add coverage for Basic auth fallback edges
Two changes to make CI green: 1. token.py: guard the Basic auth fallback assignment so pyright can narrow client_info.client_id from `str | None` to `str` before it lands in starlette's FormData (whose values are `UploadFile | str`). Semantically a no-op — ClientAuthenticator already guarantees a non-empty client_id reached this point. 2. test_auth_integration.py: cover the two previously-uncovered edges in client_auth.py's Basic auth fallback (lines 62-66): - decoded credentials without ':' separator - malformed base64 in the Authorization header Both should be silently swallowed so the request surfaces the normal "Missing client_id" error path rather than a Basic-auth-specific one. Coverage of src/mcp/server/auth/middleware/client_auth.py is now 100%. Also restores ruff-format compliance (single blank line between methods).
1 parent d425b7c commit 35fd692

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

src/mcp/server/auth/handlers/token.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ async def handle(self, request: Request):
9999
# client_id may have been supplied via HTTP Basic auth header instead of the
100100
# request body (RFC 6749 §2.3.1). ClientAuthenticator already verified it,
101101
# so we can safely populate it from client_info when absent from form data.
102-
if "client_id" not in form_data:
102+
# The truthiness check narrows `str | None` to `str` for the type checker;
103+
# ClientAuthenticator guarantees a non-empty client_id reached this point.
104+
if "client_id" not in form_data and client_info.client_id:
103105
form_data["client_id"] = client_info.client_id
104106
token_request = token_request_adapter.validate_python(form_data)
105107
except ValidationError as validation_error: # pragma: no cover

tests/server/mcpserver/auth/test_auth_integration.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1366,7 +1366,6 @@ async def test_none_auth_method_public_client(
13661366
token_response = response.json()
13671367
assert "access_token" in token_response
13681368

1369-
13701369
@pytest.mark.anyio
13711370
async def test_basic_auth_without_client_id_in_body(
13721371
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider, pkce_challenge: dict[str, str]
@@ -1454,6 +1453,34 @@ async def test_basic_auth_refresh_token_without_client_id_in_body(
14541453
token_response = response.json()
14551454
assert "access_token" in token_response
14561455

1456+
@pytest.mark.anyio
1457+
async def test_basic_auth_fallback_invalid_base64_falls_through(self, test_client: httpx.AsyncClient):
1458+
"""Basic auth fallback (no client_id in body): invalid base64 is silently ignored,
1459+
request continues without client_id and surfaces a normal 'invalid_request' error."""
1460+
# client_id missing from body AND the Basic header is malformed base64
1461+
response = await test_client.post(
1462+
"/token",
1463+
headers={"Authorization": "Basic !!!not-valid-base64!!!"},
1464+
data={"grant_type": "authorization_code", "code": "irrelevant"},
1465+
)
1466+
# The malformed base64 is swallowed; client_id remains missing → standard 401
1467+
assert response.status_code == 401
1468+
assert response.json()["error"] == "invalid_client"
1469+
1470+
@pytest.mark.anyio
1471+
async def test_basic_auth_fallback_no_colon_falls_through(self, test_client: httpx.AsyncClient):
1472+
"""Basic auth fallback (no client_id in body): decoded credentials without a colon
1473+
are not treated as a valid client_id, and the request fails with 'invalid_client'."""
1474+
# b64("no_colon_here") decodes cleanly but contains no ':' separator
1475+
encoded = base64.b64encode(b"no_colon_here").decode()
1476+
response = await test_client.post(
1477+
"/token",
1478+
headers={"Authorization": f"Basic {encoded}"},
1479+
data={"grant_type": "authorization_code", "code": "irrelevant"},
1480+
)
1481+
assert response.status_code == 401
1482+
assert response.json()["error"] == "invalid_client"
1483+
14571484

14581485
class TestAuthorizeEndpointErrors:
14591486
"""Test error handling in the OAuth authorization endpoint."""

0 commit comments

Comments
 (0)