PrefectHQ/fastmcp · error · AuthorizeError
invalid_client
invalid_client
Error message
Client ID is required
What it means
During the authorization request, the resolved ProxyDCRClient must have a client_id to store in the transaction record that the IdP callback will later consume. If client.client_id is None, authorize raises AuthorizeError with code invalid_client because the flow cannot be correlated without an ID.
Source
Thrown at fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:1180
error_description="Resource does not match this server",
)
# Generate transaction ID for this authorization request
txn_id = secrets.token_urlsafe(32)
# Generate proxy's own PKCE parameters if forwarding is enabled
proxy_code_verifier = None
proxy_code_challenge = None
if self._forward_pkce and params.code_challenge:
proxy_code_verifier, proxy_code_challenge = self._generate_pkce_pair()
logger.debug(
"Generated proxy PKCE for transaction %s (forwarding client PKCE to upstream)",
txn_id,
)
# Store transaction data for IdP callback processing
if client.client_id is None:
raise AuthorizeError(
error="invalid_client", # type: ignore[arg-type] # "invalid_client" is valid OAuth error but not in Literal type
error_description="Client ID is required",
)
# Clients may omit `scope` entirely, in which case OAuth lets the
# authorization server apply its configured default. Resolve that default
# once, here, so the transaction records the scopes actually being
# authorized. Every later consumer — the consent screen, the issued
# authorization code, token exchange, and refresh — reads this one value
# instead of deciding for itself whether to substitute required_scopes.
effective_scopes = params.scopes or self.required_scopes or []
transaction = OAuthTransaction(
txn_id=txn_id,
client_id=client.client_id,
client_redirect_uri=str(params.redirect_uri),
client_state=params.state or "",
code_challenge=params.code_challenge,
code_challenge_method=getattr(params, "code_challenge_method", "S256"),View on GitHub (pinned to 1f02114297)
Solutions
- Register the client via DCR so a valid client_id is assigned before authorizing
- Fix the client lookup/store so it returns clients with non-None client_id
- Verify the client_id query parameter sent to /authorize matches a registered client
Example fix
// before GET /authorize?client_id= # empty/unknown id -> unregistered client // after client = await register_client(...) GET /authorize?client_id=<registered_id>
Defensive patterns
Strategy: type-guard
Validate before calling
client = await proxy_client_store.get_client(requested_client_id)
if client is None or client.client_id is None:
return error_redirect("invalid_client", "Client ID is required") Type guard
def has_client_id(client) -> bool:
return client is not None and getattr(client, "client_id", None) is not None Try / catch
try:
txn = await start_authorization(client_id=..., ...)
except AuthorizeError as e:
if e.error == "invalid_client":
# re-register or correct the client_id before retrying
...
else:
raise Prevention
- Always obtain a client_id via DCR before starting an authorization flow
- Guard client store records so they can never persist with client_id=None
- Validate client_id query params at the edge before invoking authorize
When it happens
Trigger: Hitting /authorize with a client_id that resolves to a client object lacking client_id (e.g. an unregistered or malformed client record), via authorize called from _start_flow.
Common situations: Corrupted or hand-edited client store entries; custom client registries returning partially-built clients; DCR produced a client without an ID (related to error 303); test harnesses injecting dummy client objects.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- CIMD redirect_uri must have a host: {uri!r}
- CIMD document is not valid JSON: {e}
- CIMD client_id mismatch: document says '{doc.client_id}' but
- CIMD document must have jwks_uri or jwks for private_key_jwt
- Assertion must include exp claim
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/19864536480c740b.
Report an issue: GitHub.