{"record":{"id":"17fc606d627acd6e","repo":"thephpleague/oauth2-server","slug":"expired-token","errorCode":"expired_token","errorMessage":"The `device_code` has expired and the device authorization session has concluded.","messagePattern":"The `device_code` has expired and the device authorization session has concluded\\.","errorType":"http","errorClass":"OAuthServerException","httpStatus":400,"severity":"error","filePath":"src/Grant/DeviceCodeGrant.php","lineNumber":209,"sourceCode":"    {\n        $deviceCode = $this->getRequestParameter('device_code', $request);\n\n        if (is_null($deviceCode)) {\n            throw OAuthServerException::invalidRequest('device_code');\n        }\n\n        $deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(\n            $deviceCode\n        );\n\n        if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) {\n            $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));\n\n            throw OAuthServerException::invalidGrant();\n        }\n\n        if (time() > $deviceCodeEntity->getExpiryDateTime()->getTimestamp()) {\n            throw OAuthServerException::expiredToken('device_code');\n        }\n\n        if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCode) === true) {\n            throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked');\n        }\n\n        if ($deviceCodeEntity->getClient()->getIdentifier() !== $client->getIdentifier()) {\n            throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');\n        }\n\n        return $deviceCodeEntity;\n    }\n\n    private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool\n    {\n        return $lastPoll !== null && $lastPoll->getTimestamp() + $this->retryInterval > time();\n    }\n","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/thephpleague/oauth2-server/blob/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c/src/Grant/DeviceCodeGrant.php#L191-L227","documentation":"The device code was valid but its expiry timestamp has passed, ending the device authorization session. validateDeviceCode() compares time() against getExpiryDateTime() and throws expiredToken('device_code') (RFC 8628 expiry). The client must restart the device flow with a new code.","triggerScenarios":"respondToAccessTokenRequest polling a device_code after DeviceCodeGrant's deviceCodeTTL (default ~10 minutes) elapsed; user took too long to complete verification; client kept polling past expiry instead of stopping.","commonSituations":"Long approval delays (user away, email verification flow slow); clocks skewed between server nodes making codes appear expired early; client retries indefinitely on authorization_pending without checking expiry; shortened TTL configured on the server.","solutions":["Client: stop polling on expired_token and restart the device authorization flow to get a fresh code","Increase the device code TTL if your users legitimately need longer (configure the grant's device code lifetime)","Check server clock synchronization (NTP) so expiry comparisons are accurate","Handle expired_token distinctly from authorization_pending in the polling loop — it is terminal, not retryable"],"exampleFix":"// before\nif (err.error) { await sleep(interval); retry(); } // retries forever\n\n// after\nif (err.error === 'expired_token') { startDeviceFlow(); return; } // new code required","handlingStrategy":"try-catch","validationCode":"if ($entity !== null && time() > $entity->getExpiryDateTime()->getTimestamp()) { restartDeviceFlow(); }","typeGuard":null,"tryCatchPattern":"try { pollToken(); } catch (OAuthServerException $e) { if ($e->getErrorType() === 'expired_token') { restartDeviceFlow('Device code expired'); return; } throw $e; }","preventionTips":["Treat expired_token as terminal; get a fresh device code","Keep client polling loops bounded by the code TTL","Synchronize server clocks with NTP","Tune the device code TTL to realistic user approval times"],"tags":["oauth2","device-flow","token-expired"],"backgroundTag":"jwt-token-expired","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"}