{"record":{"id":"280334fe93c7075e","repo":"thephpleague/oauth2-server","slug":"invalid-client-280334","errorCode":"invalid_client","errorMessage":"invalid_client","messagePattern":"invalid_client","errorType":"http","errorClass":"OAuthServerException","httpStatus":401,"severity":"error","filePath":"src/Grant/ClientCredentialsGrant.php","lineNumber":42,"sourceCode":"/**\n * Client credentials grant class.\n */\nclass ClientCredentialsGrant extends AbstractGrant\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function respondToAccessTokenRequest(\n        ServerRequestInterface $request,\n        ResponseTypeInterface $responseType,\n        DateInterval $accessTokenTTL\n    ): ResponseTypeInterface {\n        $client = $this->validateClient($request);\n\n        if (!$client->isConfidential()) {\n            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));\n\n            throw OAuthServerException::invalidClient($request);\n        }\n\n        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));\n\n        // Finalize the requested scopes\n        $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client);\n\n        // Issue and persist access token\n        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);\n\n        // Send event to emitter\n        $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));\n\n        // Inject access token into response type\n        $responseType->setAccessToken($accessToken);\n\n        return $responseType;\n    }","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/ClientCredentialsGrant.php#L24-L60","documentation":"The OAuth server rejected the client application's credentials during the client_credentials token request. validateClient() either failed to authenticate the client or the authenticated client is not confidential (has no secret), and ClientCredentialsGrant only issues tokens to confidential clients. An CLIENT_AUTHENTICATION_FAILED event is emitted before the exception is thrown.","triggerScenarios":"Calling respondToAccessTokenRequest on ClientCredentialsGrant when: the client_id/client_secret posted (or sent via HTTP Basic PHP_AUTH_USER/PHP_AUTH_PW) do not match a registered client; the client repository returns null or a client whose secret fails hash verification; or the client resolves but isConfidential() returns false because its secret is null/empty.","commonSituations":"Client secret changed or rotated on the server but not the consumer; client registered as public (no secret) but used with client_credentials grant which requires a confidential client; missing Basic auth header behind a proxy that strips Authorization; wrong redirect of PHP_AUTH_USER when not using Basic auth; league/oauth2-server v8+ requiring isConfidential() where older versions did not.","solutions":["Send the client_secret with the request (body parameter or HTTP Basic auth) and confirm it matches the stored hashed secret","Verify the client entity returned by your ClientRepository::getClientEntity has a non-empty secret and verifySecret passes, making isConfidential() true","Check that the client record in your storage still exists and its secret hash matches (re-hash/re-save if the hashing algo changed)","If the client should be public, switch to a grant that supports public clients (e.g. authorization_code with PKCE) instead of client_credentials"],"exampleFix":"// before (public client, no secret)\n$clients['my-app'] = ['name' => 'my-app', 'redirectUri' => ''];\n\n// after (confidential client with secret)\n$clients['my-app'] = new ClientEntity('my-app', 'my-app', '', true);\n$clients['my-app']->setSecret($server->encrypt ? password_hash('s3cret', PASSWORD_DEFAULT) : 's3cret');","handlingStrategy":"try-catch","validationCode":"if (empty($clientId) || empty($clientSecret)) { throw new \\RuntimeException('client_id and client_secret are required for client_credentials'); }","typeGuard":"function isConfidentialClient(?ClientEntityInterface $c): bool { return $c !== null && $c->getSecret() !== null && $c->getSecret() !== ''; }","tryCatchPattern":"try { $token = $grant->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'invalid_client') { log('client auth failed: check id/secret and that client is confidential'); } throw $e; }","preventionTips":["Always send client_secret (body or Basic auth) for client_credentials requests","Register confidential clients with a hashed secret and verify with verifySecret","Smoke-test credentials after any rotation","Never use public (secret-less) clients with client_credentials"],"tags":["oauth2","client-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-16T09:17:16.951Z"}