{"record":{"id":"3b0b702a2dcf3b25","repo":"thephpleague/oauth2-server","slug":"slow-down","errorCode":"slow_down","errorMessage":"slow_down","messagePattern":"slow_down","errorType":"http","errorClass":"OAuthServerException","httpStatus":400,"severity":"warning","filePath":"src/Grant/DeviceCodeGrant.php","lineNumber":156,"sourceCode":"     */\n    public function respondToAccessTokenRequest(\n        ServerRequestInterface $request,\n        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","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/DeviceCodeGrant.php#L138-L174","documentation":"The device flow client is polling the token endpoint faster than the configured interval. deviceCodePolledTooSoon() compares the device code's lastPolledAt against the grant's deviceAuthInterval (default 5s); when the poll is too soon the server throws OAuthServerException::slowDown() (RFC 8628 slow_down) and the client must back off.","triggerScenarios":"respondToAccessTokenRequest polling with a device_code whose entity has lastPolledAt set and the elapsed time since it is less than the poll interval; clients ignoring the returned interval from the device authorization response and retrying immediately or in a tight loop.","commonSituations":"Client polls in a fixed short loop instead of honoring the interval returned with the device code; retrying on authorization_pending without exponential backoff; server interval raised (e.g. setInterval(10)) while clients still poll at 5s.","solutions":["Increase the client's polling interval (and back off further each time slow_down is received, per RFC 8628)","Respect the interval value returned in the device authorization JSON response","On the server, tune DeviceCodeGrant deviceAuthInterval to match expected client behavior","Only update lastPolledAt/persist when appropriate so legitimate first polls are not penalized"],"exampleFix":"// before\nsetInterval(pollToken, 3000);\n\n// after\nlet interval = deviceAuth.interval * 1000;\nsetTimeout(async function poll() {\n  try { await pollToken(); }\n  catch (e) { if (e.error === 'slow_down') interval += 5000; if (e.error === 'authorization_pending' || e.error === 'slow_down') { setTimeout(poll, interval); return; } }\n  setTimeout(poll, interval);\n}, interval);","handlingStrategy":"retry","validationCode":"$elapsed = $lastPolledAt ? (time() - $lastPolledAt->getTimestamp()) : PHP_INT_MAX; if ($elapsed < $grantInterval) { sleep($grantInterval - $elapsed); }","typeGuard":null,"tryCatchPattern":"try { pollToken(); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'slow_down') { $interval += 5; sleep($interval); retry(); } throw $e; }","preventionTips":["Honor the interval returned in the device authorization response","Back off exponentially on each slow_down per RFC 8628","Use jitter to avoid synchronized polling across many devices","Align server interval with documented client polling behavior"],"tags":["oauth2","device-flow","rate-limit","polling"],"backgroundTag":"rate-limit-exceeded","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"}