{"record":{"id":"0ffc6584c8bf187c","repo":"thephpleague/oauth2-server","slug":"invalid-request-client-secret","errorCode":null,"errorMessage":"invalid request: client_secret","messagePattern":"invalid request: client_secret","errorType":"http","errorClass":"OAuthServerException","httpStatus":400,"severity":"error","filePath":"src/Grant/AbstractGrant.php","lineNumber":158,"sourceCode":"    public function revokeRefreshTokens(bool $willRevoke): void\n    {\n        $this->revokeRefreshTokens = $willRevoke;\n    }\n\n    /**\n     * 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","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/AbstractGrant.php#L140-L176","documentation":"Validation helper (validateClient) for confidential clients: the client identified by client_id exists and is confidential, so a client_secret is mandatory, but the request supplied an empty secret (or none at all). The generic invalidRequest error is raised with the 'client_secret' parameter name to signal that the missing credential — not the id — is the fault in this grant request.","triggerScenarios":"POST /token with grant_type=password or authorization_code from a confidential client without the client_secret field in the (form-encoded) body; secret passed in header but server expects body, or empty string sent.","commonSituations":"Public SPA treating its confidential backend client as public; HTTP Basic credentials not parsed (server expects body params); empty env var for the secret deployed to production.","solutions":["Include client_secret in the token request body: client_id=...&client_secret=...&grant_type=...","If the client is genuinely public, register it as public (isConfidential() false) so no secret is required.","If using HTTP Basic auth, ensure it's enabled/expected; otherwise put the secret in the body.","Check the deployed environment actually has a non-empty CLIENT_SECRET value."],"exampleFix":"// before\nPOST /token\ngrant_type=password&client_id=web&username=u&password=p\n// after\nPOST /token\ngrant_type=password&client_id=web&client_secret=SECRET&username=u&password=p","handlingStrategy":"validation","validationCode":"if ($client->isConfidential() && ($_POST['client_secret'] ?? '') === '') {\n    throw new \\InvalidArgumentException('client_secret is required for confidential clients');\n}","typeGuard":"function hasClientSecret(array $body): bool { return isset($body['client_secret']) && is_string($body['client_secret']) && $body['client_secret'] !== ''; }","tryCatchPattern":"try { $token = $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $e) { return $e->generateHttpResponse($response); }","preventionTips":["Register SPA/mobile clients as public if they cannot keep a secret","Load CLIENT_SECRET from env and fail fast at boot if empty","Send credentials as form-encoded body unless the server is configured for HTTP Basic"],"tags":["oauth","client-authentication","missing-parameter"],"backgroundTag":"missing-credentials","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"}