{"record":{"id":"d78f239a855781fd","repo":"thephpleague/oauth2-server","slug":"unauthorized-client","errorCode":"unauthorized_client","errorMessage":"unauthorized_client","messagePattern":"unauthorized_client","errorType":"http","errorClass":"OAuthServerException","httpStatus":401,"severity":"error","filePath":"src/Grant/AbstractGrant.php","lineNumber":193,"sourceCode":"     *\n     * This is a bit of defensive coding because the interface contract\n     * doesn't actually enforce non-null returns/exception-on-no-client so\n     * getClientEntity might return null. By contrast, this method will\n     * always either return a ClientEntityInterface or throw.\n     *\n     * @throws OAuthServerException\n     */\n    protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface\n    {\n        $client = $this->clientRepository->getClientEntity($clientId);\n\n        if ($client instanceof ClientEntityInterface === false) {\n            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));\n            throw OAuthServerException::invalidClient($request);\n        }\n\n        if ($this->supportsGrantType($client, $this->getIdentifier()) === false) {\n            throw OAuthServerException::unauthorizedClient();\n        }\n\n        return $client;\n    }\n\n    /**\n     * Returns true if the given client is authorized to use the given grant type.\n     */\n    protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool\n    {\n        return method_exists($client, 'supportsGrantType') === false\n            || $client->supportsGrantType($grantType) === true;\n    }\n\n    /**\n     * Gets the client credentials from the request from the request body or\n     * the Http Basic Authorization header\n     *","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/AbstractGrant.php#L175-L211","documentation":"Thrown by AbstractGrant::getClientEntityOrFail when the client entity was found but supportsGrantType($client, $this->getIdentifier()) returns false, i.e. the client is not authorized to use the requested grant type. It throws OAuthServerException::unauthorizedClient (HTTP 401, error code 'unauthorized_client'). The client authenticated fine; it simply is not permitted to run this grant.","triggerScenarios":"Requesting a client_credentials token with a client restricted to the authorization_code grant; using the refresh_token grant with a client whose allowed grant list omits it; calling validateAuthorizationRequest or respondToDeviceAuthorizationRequest for a client whose repository data lacks the requested grant identifier; a custom ClientEntity::getGrants()/repository check that returns an empty grant list.","commonSituations":"Registering clients without populating their allowed grant types; after a library upgrade the grant identifier set changed and stored client grant lists are stale; copying a client row for a new integration but forgetting to add the new grant; mixing up a public SPA client (authorization_code only) with a backend service client.","solutions":["Add the requested grant type (e.g. 'client_credentials', 'refresh_token') to the client's allowed grants in your client store.","Check your ClientRepository/ClientEntity grant-type logic (supportsGrantType source) and make sure the grant identifier from $grant->getIdentifier() is included.","Use a separate client record with the correct grant types for service-to-service (client_credentials) usage instead of reusing the web app client.","If the grant list is stored as JSON/CSV, confirm the column is actually populated and parsed correctly."],"exampleFix":"// before (client row)\n{\"id\":\"service-a\",\"grants\":[\"authorization_code\"]}\n// after — allow the grant actually being requested\n{\"id\":\"service-a\",\"grants\":[\"authorization_code\",\"client_credentials\"]}","handlingStrategy":"try-catch","validationCode":"// verify the grant is allowed before requesting\n$allowed = in_array('client_credentials', $clientConfig['grants'] ?? [], true);\nif (!$allowed) {\n    throw new \\DomainException('client is not authorized for client_credentials grant');\n}","typeGuard":"function supportsGrant(array $clientConfig, string $grantId): bool {\n    return isset($clientConfig['grants'])\n        && is_array($clientConfig['grants'])\n        && in_array($grantId, $clientConfig['grants'], true);\n}","tryCatchPattern":"try {\n    $token = $server->respondToAccessTokenRequest($request, $response);\n} catch (OAuthServerException $e) {\n    if ($e->getErrorType() === 'unauthorized_client') {\n        // 401: the client exists but may not use this grant; point admin at client grant config\n    }\n    return $e->generateHttpResponse($response);\n}","preventionTips":["Model clients with an explicit grant_types column/list and enforce it at registration time.","Use distinct client records per integration pattern (web app vs service).","Add integration tests per grant type per client.","Review client grant lists when upgrading the library."],"tags":["oauth2","php","unauthorized-client","grant-type"],"backgroundTag":"permission-denied","analyzedSha":"9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c","analyzedAt":"2026-09-15T22:33:30.452Z","contentChangedAt":"2026-09-15T22:33:30.452Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}