thephpleague/oauth2-server · error · OAuthServerException

invalid request: client_secret

Error message

invalid request: client_secret

What it means

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.

Solutions

  1. Include client_secret in the token request body: client_id=...&client_secret=...&grant_type=...
  2. If the client is genuinely public, register it as public (isConfidential() false) so no secret is required.
  3. If using HTTP Basic auth, ensure it's enabled/expected; otherwise put the secret in the body.
  4. Check the deployed environment actually has a non-empty CLIENT_SECRET value.

Example fix

// before
POST /token
grant_type=password&client_id=web&username=u&password=p
// after
POST /token
grant_type=password&client_id=web&client_secret=SECRET&username=u&password=p
Defensive patterns

Strategy: validation

Validate before calling

if ($client->isConfidential() && ($_POST['client_secret'] ?? '') === '') {
    throw new \InvalidArgumentException('client_secret is required for confidential clients');
}

Type guard

function hasClientSecret(array $body): bool { return isset($body['client_secret']) && is_string($body['client_secret']) && $body['client_secret'] !== ''; }

Try / catch

try { $token = $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $e) { return $e->generateHttpResponse($response); }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of thephpleague/oauth2-server@9d2f6fc0a0 (2026-09-15). Data as JSON: /api/errors/0ffc6584c8bf187c. Report an issue: GitHub.

Appendix: source

Thrown at src/Grant/AbstractGrant.php:158

    public function revokeRefreshTokens(bool $willRevoke): void
    {
        $this->revokeRefreshTokens = $willRevoke;
    }

    /**
     * Validate the client.
     *
     * @throws OAuthServerException
     */
    protected function validateClient(ServerRequestInterface $request): ClientEntityInterface
    {
        [$clientId, $clientSecret] = $this->getClientCredentials($request);

        $client = $this->getClientEntityOrFail($clientId, $request);

        if ($client->isConfidential()) {
            if ($clientSecret === '') {
                throw OAuthServerException::invalidRequest('client_secret');
            }

            if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));

                throw OAuthServerException::invalidClient($request);
            }
        }

        return $client;
    }

    /**
     * Wrapper around ClientRepository::getClientEntity() that ensures we emit
     * an event and throw an exception if the repo doesn't return a client
     * entity.
     *
     * This is a bit of defensive coding because the interface contract

View on GitHub (pinned to 9d2f6fc0a0)