PrefectHQ/fastmcp · error · AuthenticationError
Invalid client_id
Error message
Invalid client_id
What it means
During OAuth client authentication at the token endpoint, the provider looks up the submitted client_id via provider.get_client(); if no registered client matches, an AuthenticationError('Invalid client_id') is raised. This only happens when client_id is present in the form body; client_secret_basic (credentials in the Authorization header) is delegated to the SDK instead.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/auth.py:268
self, request: Request
) -> OAuthClientInformationFull:
"""Authenticate a client from an HTTP request.
Extends SDK authentication to support private_key_jwt for CIMD clients.
Delegates to SDK for client_secret_basic (Authorization header) and
client_secret_post (form body) authentication.
"""
form_data = await request.form()
client_id = form_data.get("client_id")
# If client_id is not in form data, delegate to SDK
# This handles client_secret_basic which sends credentials in Authorization header
if not client_id:
return await super().authenticate_request(request)
client = await self.provider.get_client(str(client_id))
if not client:
raise AuthenticationError("Invalid client_id")
# Handle private_key_jwt authentication for CIMD clients
if client.token_endpoint_auth_method == "private_key_jwt":
# Validate assertion parameters
assertion_type = form_data.get("client_assertion_type")
assertion = form_data.get("client_assertion")
if assertion_type != JWT_BEARER_ASSERTION_TYPE:
raise AuthenticationError(
f"Invalid client_assertion_type: expected {JWT_BEARER_ASSERTION_TYPE}"
)
if not assertion or not isinstance(assertion, str):
raise AuthenticationError("Missing client_assertion")
# Validate the JWT assertion using CIMD manager
try:
await self._cimd_manager.validate_private_key_jwt(View on GitHub (pinned to 1f02114297)
Solutions
- Verify the client_id sent by the OAuth client exactly matches a registered client in your provider's store.
- Re-register the client (or fix the client's configuration) so get_client() returns its information.
- Confirm you're hitting the right environment — registration data differs between dev/staging/prod.
- For CIMD clients, ensure the client_id metadata URL is reachable and valid, or fall back to a registered client_id.
Example fix
// before # client config client_id = "my-app" # not registered on this server // after # register first, then use the issued id client_id = "registered-client-id-from-provider"
Defensive patterns
Strategy: validation
Validate before calling
async def client_id_is_registered(provider, client_id: str) -> bool:
return await provider.get_client(client_id) is not None Try / catch
# server side (returned to client as OAuth error)
try:
client = await provider.get_client(client_id)
except AuthenticationError as e:
return JSONResponse(status_code=401, content={'error': 'invalid_client'}) Prevention
- Keep client registrations in sync across environments.
- Use automated client registration (RFC 7591) where possible.
- Log the submitted client_id (not secrets) on failure to speed diagnosis.
When it happens
Trigger: POST to the token endpoint with client_id in form data whose value is not registered with the OAuth provider (auth.py:266-268) — e.g. client was deleted, wrong environment, or a CIMD/private_key_jwt client whose metadata URL isn't fetchable/registered.
Common situations: Client credentials from dev/staging used against a production server; OAuth client registration never completed or was rotated; CIMD client_id (an HTTPS URL) unreachable or mistyped; clients configured with client_secret_basic still putting client_id in the body with a mismatched secret.
Related errors
- Missing client_assertion
- Invalid JWT assertion
- The device authorization request expired
- The device authorization request was denied
- Device authorization failed
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/3f1d2c1bc1e64046.
Report an issue: GitHub.