{"record":{"id":"c406b1bf46ed3f7d","repo":"thephpleague/oauth2-server","slug":"client-authentication-failed","errorCode":null,"errorMessage":"client authentication failed","messagePattern":"client authentication failed","errorType":"http","errorClass":"OAuthServerException","httpStatus":401,"severity":"error","filePath":"src/Grant/AbstractGrant.php","lineNumber":164,"sourceCode":"     * Validate the client.\n     *\n     * @throws OAuthServerException\n     */\n    protected function validateClient(ServerRequestInterface $request): ClientEntityInterface\n    {\n        [$clientId, $clientSecret] = $this->getClientCredentials($request);\n\n        $client = $this->getClientEntityOrFail($clientId, $request);\n\n        if ($client->isConfidential()) {\n            if ($clientSecret === '') {\n                throw OAuthServerException::invalidRequest('client_secret');\n            }\n\n            if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {\n                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));\n\n                throw OAuthServerException::invalidClient($request);\n            }\n        }\n\n        return $client;\n    }\n\n    /**\n     * Wrapper around ClientRepository::getClientEntity() that ensures we emit\n     * an event and throw an exception if the repo doesn't return a client\n     * entity.\n     *\n     * This is a bit of defensive coding because the interface contract\n     * doesn't actually enforce non-null returns/exception-on-no-client so\n     * getClientEntity might return null. By contrast, this method will\n     * always either return a ClientEntityInterface or throw.\n     *\n     * @throws OAuthServerException\n     */","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/AbstractGrant.php#L146-L182","documentation":"This error is thrown by AbstractGrant::validateClient when ClientRepository::validateClient() returns false, meaning the client credentials supplied with the token request (client_id/client_secret, from body or HTTP Basic auth) do not authenticate for the given grant type. The library emits a CLIENT_AUTHENTICATION_FAILED event and then throws OAuthServerException::invalidClient, surfacing as HTTP 401 with error 'invalid_client'. It is the library's way of rejecting failed confidential-client authentication during respondToAccessTokenRequest.","triggerScenarios":"Calling $server->respondToAccessTokenRequest() on a password, client_credentials, refresh_token (with secret required) or authorization_code grant when: the client_secret is wrong or missing; the client_id exists but the secret does not match; validateClient() in the custom ClientRepository explicitly returns false (e.g. the client is revoked, or the grant type is not in the client's allowed list); credentials sent via Basic auth header are mis-decoded or the header is malformed.","commonSituations":"Secrets rotated in production but not in the client app; env var for client secret missing/empty on deploy; storing secrets with trailing whitespace or quotes picked up from .env; client registered as public but used with a secret (or vice versa) so the repository's validateClient logic rejects it; custom ClientRepository implementations that return false instead of null for unknown clients.","solutions":["Verify the client_id and client_secret values sent in the token request match what your ClientRepository::validateClient expects (check env vars, trailing whitespace, quotes).","Inspect your ClientRepository::validateClient implementation and log why it returns false (unknown client, bad secret, grant not allowed, revoked client).","If using HTTP Basic auth, confirm the Authorization header is 'Basic base64(urlencode(client_id):urlencode(client_secret))' and that your server does not strip it.","Ensure the grant type being requested is enabled for that client in your repository lookup.","Listen for the RequestEvent::CLIENT_AUTHENTICATION_FAILED event to log the client_id and add diagnostics."],"exampleFix":"// before (custom repository rejecting everything)\npublic function validateClient($clientId, $clientSecret, $grantType): bool {\n    return $this->clients[$clientId]['secret'] === $clientSecret; // fails when client unknown\n}\n// after\npublic function validateClient($clientId, $clientSecret, $grantType): bool {\n    $client = $this->clients[$clientId] ?? null;\n    if ($client === null) { return false; }\n    return hash_equals($client['secret'], (string) $clientSecret)\n        && in_array($grantType, $client['grant_types'], true);\n}","handlingStrategy":"try-catch","validationCode":"// caller-side pre-check\nif (empty($clientId) || empty($clientSecret)) {\n    throw new \\RuntimeException('client_id and client_secret must be set before token exchange');\n}","typeGuard":"function hasClientCredentials(array $params): bool {\n    return isset($params['client_id'], $params['client_secret'])\n        && is_string($params['client_id']) && $params['client_id'] !== ''\n        && is_string($params['client_secret']) && $params['client_secret'] !== '';\n}","tryCatchPattern":"try {\n    $token = $server->respondToAccessTokenRequest($request, $response);\n} catch (OAuthServerException $e) {\n    if ($e->getCode() === 4 || $e->getErrorType() === 'invalid_client') {\n        // 401: log client_id, surface 'check client credentials' to caller\n    }\n    return $e->generateHttpResponse($response);\n}","preventionTips":["Store client secrets out of code and validate env vars at boot (fail fast if empty).","Use hash_equals for secret comparison in custom repositories.","Log CLIENT_AUTHENTICATION_FAILED events with client_id for auditing.","Rotate secrets via a documented deploy step so client and server update together."],"tags":["oauth2","php","authentication","client-credentials"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c","analyzedAt":"2026-09-15T22:33:30.452Z","contentChangedAt":"2026-09-15T22:33:30.452Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}