{"record":{"id":"7709929a22ee79e5","repo":"thephpleague/oauth2-server","slug":"authorization-pending","errorCode":"authorization_pending","errorMessage":"authorization_pending","messagePattern":"authorization_pending","errorType":"http","errorClass":"OAuthServerException","httpStatus":400,"severity":"info","filePath":"src/Grant/DeviceCodeGrant.php","lineNumber":159,"sourceCode":"        ResponseTypeInterface $responseType,\n        DateInterval $accessTokenTTL\n    ): ResponseTypeInterface {\n        // Validate request\n        $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) {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/DeviceCodeGrant.php#L141-L177","documentation":"The device code is valid but the user has not yet completed the verification step (or polling occurred before lastPolledAt handling marked this poll). RFC 8628 authorization_pending: the client should keep polling at the given interval until the user approves, denies, or the code expires.","triggerScenarios":"respondToAccessTokenRequest finds a valid, non-expired device code whose lastPolledAt is null or old enough (not slow_down) but whose userApproved flag is still false/unset — i.e. normal polling before the user finishes approving at the verification URI.","commonSituations":"The expected state during device flow while the user is still typing the user_code; client treats it as a fatal error and aborts instead of continuing to poll; UI never shows the verification URI so the user never approves.","solutions":["Treat authorization_pending as transient: keep polling at the specified interval until approved/denied/expired","Make sure the device authorization response's verification_uri (and verification_uri_complete) are surfaced to the user","Ensure completeDeviceAuthorizationRequest is actually wired to your verification page so userApproved gets set","Check expiry handling: switch to expired_token handling once the device code TTL passes"],"exampleFix":"// before\nconst token = await pollToken(deviceCode); // throws, request dies\n\n// after\nwhile (true) {\n  try { const token = await pollToken(deviceCode); break; }\n  catch (e) {\n    if (e.error === 'authorization_pending' || e.error === 'slow_down') { await sleep(interval); continue; }\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"$entity = $repo->getDeviceCodeEntityByDeviceCode($code); if ($entity !== null && $entity->getUserApproved() === false) { keepPolling(); }","typeGuard":null,"tryCatchPattern":"try { pollToken(); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'authorization_pending') { sleep($interval); retry(); } throw $e; }","preventionTips":["Treat authorization_pending as normal device-flow progress, not an error","Always display verification_uri/verification_uri_complete to the user","Cap total polling time to the device code TTL","Wire completeDeviceAuthorizationRequest properly so approval eventually lands"],"tags":["oauth2","device-flow","polling"],"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-16T04:17:20.429Z"}