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
- 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.
- If using Basic auth on Apache, add CGIPassAuth On (or the classic RewriteRule passthrough) so PHP receives the Authorization header.
- Check the parameter name is exactly 'client_id' (snake case) and non-empty.
- 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
- Always send token requests as application/x-www-form-urlencoded.
- On Apache/PHP-FPM set CGIPassAuth On so Authorization headers reach PHP.
- Centralize token requests in one client helper that asserts required params first.
- Check env config for client_id at application boot.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
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)