thephpleague/oauth2-server · error · OAuthServerException

invalid_request

invalid_request

Error message

The request is missing a required parameter, is invalid, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Check the "client_id" parameter

What it means

Thrown by AbstractGrant::getClientCredentials when neither a 'client_id' request parameter nor an HTTP Basic auth username is present. OAuthServerException::invalidRequest('client_id') produces an HTTP 400 invalid_request error telling the caller the client_id parameter is missing or malformed. The grant cannot even identify the client, so authentication never begins.

Solutions

  1. Include client_id (and client_secret) as application/x-www-form-urlencoded body parameters in the token request, or send them via a proper Authorization: Basic header.
  2. If using Basic auth on Apache, add CGIPassAuth On (or the classic RewriteRule passthrough) so PHP receives the Authorization header.
  3. Check the parameter name is exactly 'client_id' (snake case) and non-empty.
  4. Confirm the request Content-Type is application/x-www-form-urlencoded, not JSON, since getParsedBody won't parse JSON by default.

Example fix

// before
curl -X POST https://idp/token -H 'Content-Type: application/json' -d '{"clientId":"abc"}'
// after
curl -X POST https://idp/token -d 'grant_type=client_credentials&client_id=abc&client_secret=xyz'
Defensive patterns

Strategy: validation

Validate before calling

// validate the outgoing token request before sending
$params = ['grant_type' => 'client_credentials', 'client_id' => $clientId, 'client_secret' => $secret];
foreach (['client_id', 'client_secret'] as $k) {
    if (!isset($params[$k]) || !is_string($params[$k]) || $params[$k] === '') {
        throw new \InvalidArgumentException("token request missing {$k}");
    }
}
http_build_query($params); // send as form-encoded body

Type guard

function hasClientId(array $parsedBody, ?array $serverParams = null): bool {
    if (isset($parsedBody['client_id']) && $parsedBody['client_id'] !== '') { return true; }
    $auth = $serverParams['HTTP_AUTHORIZATION'] ?? '';
    return str_starts_with($auth, 'Basic ');
}

Try / catch

// this error is a 400 invalid_request; catch and give actionable feedback
try {
    $token = $server->respondToAccessTokenRequest($request, $response);
} catch (OAuthServerException $e) {
    if ($e->getErrorType() === 'invalid_request' && str_contains($e->getMessage(), 'client_id')) {
        // caller forgot client_id / Basic header
    }
    return $e->generateHttpResponse($response);
}

Prevention

When it happens

Trigger: POSTing to the token endpoint (respondToAccessTokenRequest) with no client_id in the body and no Authorization: Basic header; sending the credentials in a header your web server strips (e.g. missing mod_rewrite/SetEnvIf Authorization passthrough under Apache+PHP-FPM); sending client_id under a wrong key name (e.g. clientId); sending an empty client_id parameter.

Common situations: Apache behind a proxy dropping the Authorization header (very common with PHP); frontend sending JSON body while the endpoint expects form-encoded params; curl examples using -d client_id=... but forgetting the ampersand-separated second param; tests calling the server with a request missing the body params.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at src/Grant/AbstractGrant.php:223

            || $client->supportsGrantType($grantType) === true;
    }

    /**
     * Gets the client credentials from the request from the request body or
     * the Http Basic Authorization header
     *
     * @return array{0:non-empty-string,1:string}
     *
     * @throws OAuthServerException
     */
    protected function getClientCredentials(ServerRequestInterface $request): array
    {
        [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request);

        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);

        if ($clientId === null) {
            throw OAuthServerException::invalidRequest('client_id');
        }

        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);

        return [$clientId, $clientSecret ?? ''];
    }

    /**
     * Validate redirectUri from the request. If a redirect URI is provided
     * ensure it matches what is pre-registered
     *
     * @throws OAuthServerException
     */
    protected function validateRedirectUri(
        string $redirectUri,
        ClientEntityInterface $client,
        ServerRequestInterface $request
    ): void {

View on GitHub (pinned to 9d2f6fc0a0)