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

  1. Confirm the client_id in the request exists in your client store and that getClientEntity returns a ClientEntityInterface instance for it.
  2. 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.
  3. Verify the client_id string matches exactly (case sensitivity, whitespace, URL-encoding issues).
  4. 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

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)