thephpleague/oauth2-server · error · OAuthServerException
invalid_client
invalid_client
Error message
invalid_client
What it means
Thrown by AbstractGrant::getClientEntityOrFail when ClientRepository::getClientEntity($clientId) returns anything that is not a ClientEntityInterface (typically null), meaning no client entity exists for the given client_id. The grant emits CLIENT_AUTHENTICATION_FAILED and throws OAuthServerException::invalidClient (HTTP 401, error code 'invalid_client'). Unlike error 10, this is about client lookup/resolution failing, not secret verification.
Solutions
- Confirm the client_id in the request exists in your client store and that getClientEntity returns a ClientEntityInterface instance for it.
- Check your ClientRepository::getClientEntity implementation: make sure it returns the entity, not null/array, and that any active/revoked filtering is not hiding the client.
- Verify the client_id string matches exactly (case sensitivity, whitespace, URL-encoding issues).
- Ensure the authorization server instance uses the ClientRepository you think it does (DI/container wiring).
Example fix
// before
public function getClientEntity($clientIdentifier) {
return $this->pdo->query("SELECT * FROM clients WHERE id = ?", [$clientIdentifier]); // returns array
}
// after
public function getClientEntity($clientIdentifier): ?ClientEntityInterface {
$row = /* fetch row */;
if ($row === null) { return null; }
$client = new ClientEntity();
$client->setIdentifier($row['id']);
$client->setRedirectUri(json_decode($row['redirect_uris'], true));
$client->setName($row['name']);
return $client;
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight lookup in your own store before calling the server
$stmt = $pdo->prepare('SELECT id FROM oauth_clients WHERE id = ? AND revoked = 0');
$stmt->execute([$clientId]);
if ($stmt->fetch() === false) {
throw new \InvalidArgumentException("Unknown client_id: {$clientId}");
} Type guard
function isClientFound($clientEntity): bool {
return $clientEntity instanceof \League\OAuth2\Server\Entities\ClientEntityInterface;
} Try / catch
try {
$authRequest = $server->validateAuthorizationRequest($request);
} catch (OAuthServerException $e) {
if ($e->getErrorType() === 'invalid_client') {
// client_id not resolvable: return 401 with helpful message
}
return $e->generateHttpResponse($response);
} Prevention
- Unit-test ClientRepository::getClientEntity for both found and not-found client_ids.
- Always return a ClientEntityInterface or null — never arrays/false — from getClientEntity.
- Keep client registration in sync between environments with seed scripts.
- Trim and normalize client_id input before lookup.
When it happens
Trigger: Any grant flow (validateClient, validateAuthorizationRequest, respondToDeviceAuthorizationRequest) where the client_id passed in the request is not found by your ClientRepository::getClientEntity, or where your repository returns a non-ClientEntity value (null, array, false).
Common situations: Typo in client_id in the frontend config; client deleted from the database but still cached in the SPA; getClientEntity returning null because the lookup filters by is_revoked/active flag; a custom repository that returns an array or stdclass instead of a ClientEntityInterface instance; forgetting to register the client in a seeded dev database.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — 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/07be5e2713994011.
Report an issue: GitHub.
Appendix: source
Thrown at src/Grant/AbstractGrant.php:189
/**
* 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
* doesn't actually enforce non-null returns/exception-on-no-client so
* getClientEntity might return null. By contrast, this method will
* always either return a ClientEntityInterface or throw.
*
* @throws OAuthServerException
*/
protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface
{
$client = $this->clientRepository->getClientEntity($clientId);
if ($client instanceof ClientEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
}
if ($this->supportsGrantType($client, $this->getIdentifier()) === false) {
throw OAuthServerException::unauthorizedClient();
}
return $client;
}
/**
* Returns true if the given client is authorized to use the given grant type.
*/
protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool
{
return method_exists($client, 'supportsGrantType') === false
|| $client->supportsGrantType($grantType) === true;
}
View on GitHub (pinned to 9d2f6fc0a0)