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

  1. Register the client via DCR so a valid client_id is assigned before authorizing
  2. Fix the client lookup/store so it returns clients with non-None client_id
  3. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/19864536480c740b. Report an issue: GitHub.