Skip to content

Commit 68fa608

Browse files
author
Jay Hemnani
committed
fix: respect token_endpoint_auth_method=none when client_secret exists
When token_endpoint_auth_method is set to "none", the ClientAuthenticator should not require a client_secret on the request, even if a client_secret is stored for that client. This can happen if a client was previously registered with a secret but later changed to public authentication. The fix adds a check for token_endpoint_auth_method != "none" before validating the client_secret requirement. Fixes #1842 Github-Issue: #1842 Reported-by: aiwebb
1 parent 6b69f63 commit 68fa608

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

src/mcp/server/auth/middleware/client_auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
9797
f"Unsupported auth method: {client.token_endpoint_auth_method}"
9898
)
9999

100-
# If client from the store expects a secret, validate that the request provides
101-
# that secret
102-
if client.client_secret: # pragma: no branch
100+
# If client from the store expects a secret and the auth method requires it,
101+
# validate that the request provides that secret
102+
if client.token_endpoint_auth_method != "none" and client.client_secret:
103103
if not request_client_secret:
104104
raise AuthenticationError("Client secret is required") # pragma: no cover
105105

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,75 @@ async def test_none_auth_method_public_client(
13711371
token_response = response.json()
13721372
assert "access_token" in token_response
13731373

1374+
@pytest.mark.anyio
1375+
async def test_none_auth_method_ignores_stored_client_secret(
1376+
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider, pkce_challenge: dict[str, str]
1377+
):
1378+
"""Test that 'none' auth method works even if client has a stored secret.
1379+
1380+
This tests the fix for issue #1842: when token_endpoint_auth_method="none",
1381+
the server should not require client_secret even if one is stored for the client.
1382+
This can happen if a client was previously registered with a secret but later
1383+
changed to public authentication.
1384+
"""
1385+
# Register a public client with token_endpoint_auth_method="none"
1386+
client_metadata = {
1387+
"redirect_uris": ["https://client.example.com/callback"],
1388+
"client_name": "Public Client With Stored Secret",
1389+
"token_endpoint_auth_method": "none",
1390+
"grant_types": ["authorization_code", "refresh_token"],
1391+
}
1392+
1393+
response = await test_client.post("/register", json=client_metadata)
1394+
assert response.status_code == 201
1395+
client_info = response.json()
1396+
assert client_info["token_endpoint_auth_method"] == "none"
1397+
1398+
# Manually add a client_secret to the stored client (simulating edge case)
1399+
stored_client = await mock_oauth_provider.get_client(client_info["client_id"])
1400+
assert stored_client is not None
1401+
# Create a modified client with a secret
1402+
modified_client = OAuthClientInformationFull(
1403+
client_id=stored_client.client_id,
1404+
client_id_issued_at=stored_client.client_id_issued_at,
1405+
client_secret="secret_that_should_be_ignored",
1406+
client_secret_expires_at=None,
1407+
redirect_uris=stored_client.redirect_uris,
1408+
token_endpoint_auth_method="none", # Still using 'none' auth method
1409+
grant_types=stored_client.grant_types,
1410+
response_types=stored_client.response_types,
1411+
client_name=stored_client.client_name,
1412+
scope=stored_client.scope,
1413+
)
1414+
mock_oauth_provider.clients[client_info["client_id"]] = modified_client
1415+
1416+
auth_code = f"code_{int(time.time())}"
1417+
mock_oauth_provider.auth_codes[auth_code] = AuthorizationCode(
1418+
code=auth_code,
1419+
client_id=client_info["client_id"],
1420+
code_challenge=pkce_challenge["code_challenge"],
1421+
redirect_uri=AnyUrl("https://client.example.com/callback"),
1422+
redirect_uri_provided_explicitly=True,
1423+
scopes=["read", "write"],
1424+
expires_at=time.time() + 600,
1425+
)
1426+
1427+
# Token request without any client secret should still succeed
1428+
# because token_endpoint_auth_method="none"
1429+
response = await test_client.post(
1430+
"/token",
1431+
data={
1432+
"grant_type": "authorization_code",
1433+
"client_id": client_info["client_id"],
1434+
"code": auth_code,
1435+
"code_verifier": pkce_challenge["code_verifier"],
1436+
"redirect_uri": "https://client.example.com/callback",
1437+
},
1438+
)
1439+
assert response.status_code == 200
1440+
token_response = response.json()
1441+
assert "access_token" in token_response
1442+
13741443

13751444
class TestAuthorizeEndpointErrors:
13761445
"""Test error handling in the OAuth authorization endpoint."""

0 commit comments

Comments
 (0)