{"record":{"id":"19864536480c740b","repo":"PrefectHQ/fastmcp","slug":"invalid-client","errorCode":"invalid_client","errorMessage":"Client ID is required","messagePattern":"Client ID is required","errorType":"error_code","errorClass":"AuthorizeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py","lineNumber":1180,"sourceCode":"                    error_description=\"Resource does not match this server\",\n                )\n\n        # Generate transaction ID for this authorization request\n        txn_id = secrets.token_urlsafe(32)\n\n        # Generate proxy's own PKCE parameters if forwarding is enabled\n        proxy_code_verifier = None\n        proxy_code_challenge = None\n        if self._forward_pkce and params.code_challenge:\n            proxy_code_verifier, proxy_code_challenge = self._generate_pkce_pair()\n            logger.debug(\n                \"Generated proxy PKCE for transaction %s (forwarding client PKCE to upstream)\",\n                txn_id,\n            )\n\n        # Store transaction data for IdP callback processing\n        if client.client_id is None:\n            raise AuthorizeError(\n                error=\"invalid_client\",  # type: ignore[arg-type]  # \"invalid_client\" is valid OAuth error but not in Literal type\n                error_description=\"Client ID is required\",\n            )\n        # Clients may omit `scope` entirely, in which case OAuth lets the\n        # authorization server apply its configured default. Resolve that default\n        # once, here, so the transaction records the scopes actually being\n        # authorized. Every later consumer — the consent screen, the issued\n        # authorization code, token exchange, and refresh — reads this one value\n        # instead of deciding for itself whether to substitute required_scopes.\n        effective_scopes = params.scopes or self.required_scopes or []\n\n        transaction = OAuthTransaction(\n            txn_id=txn_id,\n            client_id=client.client_id,\n            client_redirect_uri=str(params.redirect_uri),\n            client_state=params.state or \"\",\n            code_challenge=params.code_challenge,\n            code_challenge_method=getattr(params, \"code_challenge_method\", \"S256\"),","sourceCodeStart":1162,"sourceCodeEnd":1198,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py#L1162-L1198","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nGET /authorize?client_id=  # empty/unknown id -> unregistered client\n// after\nclient = await register_client(...)\nGET /authorize?client_id=<registered_id>","handlingStrategy":"type-guard","validationCode":"client = await proxy_client_store.get_client(requested_client_id)\nif client is None or client.client_id is None:\n    return error_redirect(\"invalid_client\", \"Client ID is required\")","typeGuard":"def has_client_id(client) -> bool:\n    return client is not None and getattr(client, \"client_id\", None) is not None","tryCatchPattern":"try:\n    txn = await start_authorization(client_id=..., ...)\nexcept AuthorizeError as e:\n    if e.error == \"invalid_client\":\n        # re-register or correct the client_id before retrying\n        ...\n    else:\n        raise","preventionTips":["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"],"tags":["oauth","authorize","client-id","validation"],"backgroundTag":"missing-required-field","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}