{"record":{"id":"7cc164b4596fae6f","repo":"thephpleague/oauth2-server","slug":"access-denied-7cc164","errorCode":"access_denied","errorMessage":"access_denied","messagePattern":"access_denied","errorType":"http","errorClass":"OAuthServerException","httpStatus":401,"severity":"error","filePath":"src/Grant/DeviceCodeGrant.php","lineNumber":163,"sourceCode":"        $client = $this->validateClient($request);\n        $deviceCodeEntity = $this->validateDeviceCode($request, $client);\n\n        // If device code has no user associated, respond with pending or slow down\n        if (is_null($deviceCodeEntity->getUserIdentifier())) {\n            $shouldSlowDown = $this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt());\n\n            $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());\n            $this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity);\n\n            if ($shouldSlowDown) {\n                throw OAuthServerException::slowDown();\n            }\n\n            throw OAuthServerException::authorizationPending();\n        }\n\n        if ($deviceCodeEntity->getUserApproved() === false) {\n            throw OAuthServerException::accessDenied();\n        }\n\n        // Finalize the requested scopes\n        $finalizedScopes = $this->scopeRepository->finalizeScopes($deviceCodeEntity->getScopes(), $this->getIdentifier(), $client, $deviceCodeEntity->getUserIdentifier());\n\n        // Issue and persist new access token\n        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCodeEntity->getUserIdentifier(), $finalizedScopes);\n        $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));\n        $responseType->setAccessToken($accessToken);\n\n        // Issue and persist new refresh token if given\n        $refreshToken = $this->issueRefreshToken($accessToken);\n\n        if ($refreshToken !== null) {\n            $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));\n            $responseType->setRefreshToken($refreshToken);\n        }\n","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/DeviceCodeGrant.php#L145-L181","documentation":"The user explicitly denied the authorization request on the verification page (getUserApproved() === false), so the grant throws OAuthServerException::accessDenied() per RFC 6749/8628. The device flow session is over; the client must stop polling and surface the denial to the user.","triggerScenarios":"respondToAccessTokenRequest on a valid, non-expired device code after completeDeviceAuthorizationRequest was called with $userApproved = false — typically the user clicked 'Deny' (or your UI defaulted to false) on the verification URI page.","commonSituations":"Verification page 'Cancel'/'Deny' button wired to completeDeviceAuthorizationRequest(..., false); user approves the wrong device and denies; a default false value when the verification form posts without an approve flag.","solutions":["Treat access_denied as terminal on the client: stop polling and inform the user authorization was refused","If the denial was accidental, restart the device flow with a fresh device code","Verify the verification page sends an explicit approve=true when the user consents so false is only sent deliberately","Do not retry polling after access_denied — it will keep failing"],"exampleFix":"// before\nif (err.error) retryPoll(); // retries forever on access_denied\n\n// after\nif (err.error === 'access_denied') { ui.showError('You denied access on your device.'); stopPolling(); }","handlingStrategy":"try-catch","validationCode":"$entity = $repo->getDeviceCodeEntityByDeviceCode($code); if ($entity !== null && $entity->getUserApproved() === false && $wasExplicitDenial) { showDenied(); exit; }","typeGuard":null,"tryCatchPattern":"try { pollToken(); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'access_denied') { stopPolling(); ui.notify('Authorization was denied'); return; } throw $e; }","preventionTips":["Stop polling immediately when access_denied is returned","Only post userApproved=false when the user explicitly denies","Provide an explicit approve action that sets userApproved=true","Restart the flow with a new device code if the user changes their mind"],"tags":["oauth2","device-flow","user-consent"],"backgroundTag":"invalid-state-transition","analyzedSha":"9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c","analyzedAt":"2026-09-15T22:33:30.452Z","contentChangedAt":"2026-09-15T22:33:30.452Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}