thephpleague/oauth2-server · warning · OAuthServerException

slow_down

slow_down

Error message

slow_down

What it means

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.

Solutions

  1. Increase the client's polling interval (and back off further each time slow_down is received, per RFC 8628)
  2. Respect the interval value returned in the device authorization JSON response
  3. On the server, tune DeviceCodeGrant deviceAuthInterval to match expected client behavior
  4. Only update lastPolledAt/persist when appropriate so legitimate first polls are not penalized

Example fix

// before
setInterval(pollToken, 3000);

// after
let interval = deviceAuth.interval * 1000;
setTimeout(async function poll() {
  try { await pollToken(); }
  catch (e) { if (e.error === 'slow_down') interval += 5000; if (e.error === 'authorization_pending' || e.error === 'slow_down') { setTimeout(poll, interval); return; } }
  setTimeout(poll, interval);
}, interval);
Defensive patterns

Strategy: retry

Validate before calling

$elapsed = $lastPolledAt ? (time() - $lastPolledAt->getTimestamp()) : PHP_INT_MAX; if ($elapsed < $grantInterval) { sleep($grantInterval - $elapsed); }

Try / catch

try { pollToken(); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'slow_down') { $interval += 5; sleep($interval); retry(); } throw $e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of thephpleague/oauth2-server@9d2f6fc0a0 (2026-09-15). Data as JSON: /api/errors/3b0b702a2dcf3b25. Report an issue: GitHub.

Appendix: source

Thrown at src/Grant/DeviceCodeGrant.php:156

     */
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        DateInterval $accessTokenTTL
    ): ResponseTypeInterface {
        // Validate request
        $client = $this->validateClient($request);
        $deviceCodeEntity = $this->validateDeviceCode($request, $client);

        // If device code has no user associated, respond with pending or slow down
        if (is_null($deviceCodeEntity->getUserIdentifier())) {
            $shouldSlowDown = $this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt());

            $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());
            $this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity);

            if ($shouldSlowDown) {
                throw OAuthServerException::slowDown();
            }

            throw OAuthServerException::authorizationPending();
        }

        if ($deviceCodeEntity->getUserApproved() === false) {
            throw OAuthServerException::accessDenied();
        }

        // Finalize the requested scopes
        $finalizedScopes = $this->scopeRepository->finalizeScopes($deviceCodeEntity->getScopes(), $this->getIdentifier(), $client, $deviceCodeEntity->getUserIdentifier());

        // Issue and persist new access token
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCodeEntity->getUserIdentifier(), $finalizedScopes);
        $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));
        $responseType->setAccessToken($accessToken);

        // Issue and persist new refresh token if given

View on GitHub (pinned to 9d2f6fc0a0)