Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/mcp/server/auth/middleware/client_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
f"Unsupported auth method: {client.token_endpoint_auth_method}"
)

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

Expand Down
69 changes: 69 additions & 0 deletions tests/server/fastmcp/auth/test_auth_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1371,6 +1371,75 @@ async def test_none_auth_method_public_client(
token_response = response.json()
assert "access_token" in token_response

@pytest.mark.anyio
async def test_none_auth_method_ignores_stored_client_secret(
self, test_client: httpx.AsyncClient, mock_oauth_provider: MockOAuthProvider, pkce_challenge: dict[str, str]
):
"""Test that 'none' auth method works even if client has a stored secret.

This tests the fix for issue #1842: when token_endpoint_auth_method="none",
the server should not require client_secret even if one is stored for the client.
This can happen if a client was previously registered with a secret but later
changed to public authentication.
"""
# Register a public client with token_endpoint_auth_method="none"
client_metadata = {
"redirect_uris": ["https://client.example.com/callback"],
"client_name": "Public Client With Stored Secret",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
}

response = await test_client.post("/register", json=client_metadata)
assert response.status_code == 201
client_info = response.json()
assert client_info["token_endpoint_auth_method"] == "none"

# Manually add a client_secret to the stored client (simulating edge case)
stored_client = await mock_oauth_provider.get_client(client_info["client_id"])
assert stored_client is not None
# Create a modified client with a secret
modified_client = OAuthClientInformationFull(
client_id=stored_client.client_id,
client_id_issued_at=stored_client.client_id_issued_at,
client_secret="secret_that_should_be_ignored",
client_secret_expires_at=None,
redirect_uris=stored_client.redirect_uris,
token_endpoint_auth_method="none", # Still using 'none' auth method
grant_types=stored_client.grant_types,
response_types=stored_client.response_types,
client_name=stored_client.client_name,
scope=stored_client.scope,
)
mock_oauth_provider.clients[client_info["client_id"]] = modified_client

auth_code = f"code_{int(time.time())}"
mock_oauth_provider.auth_codes[auth_code] = AuthorizationCode(
code=auth_code,
client_id=client_info["client_id"],
code_challenge=pkce_challenge["code_challenge"],
redirect_uri=AnyUrl("https://client.example.com/callback"),
redirect_uri_provided_explicitly=True,
scopes=["read", "write"],
expires_at=time.time() + 600,
)

# Token request without any client secret should still succeed
# because token_endpoint_auth_method="none"
response = await test_client.post(
"/token",
data={
"grant_type": "authorization_code",
"client_id": client_info["client_id"],
"code": auth_code,
"code_verifier": pkce_challenge["code_verifier"],
"redirect_uri": "https://client.example.com/callback",
},
)
assert response.status_code == 200
token_response = response.json()
assert "access_token" in token_response


class TestAuthorizeEndpointErrors:
"""Test error handling in the OAuth authorization endpoint."""
Expand Down
Loading