{"record":{"id":"7f0fbbc21fb8e6b6","repo":"BookStackApp/BookStack","slug":"token-issue-at-time-is-not-recent-or-is-invalid","errorCode":null,"errorMessage":"Token issue at time is not recent or is invalid","messagePattern":"Token issue at time is not recent or is invalid","errorType":"exception","errorClass":"OidcInvalidTokenException","httpStatus":null,"severity":"error","filePath":"app/Access/Oidc/OidcIdToken.php","lineNumber":73,"sourceCode":"        }\n\n        $skewSeconds = 120;\n        $now = time();\n        if ($now >= (intval($this->payload['exp']) + $skewSeconds)) {\n            throw new OidcInvalidTokenException('Token has expired');\n        }\n\n        // 6. The iat Claim can be used to reject tokens that were issued too far away from the current time,\n        // limiting the amount of time that nonces need to be stored to prevent attacks.\n        // The acceptable range is Client specific.\n        if (empty($this->payload['iat'])) {\n            throw new OidcInvalidTokenException('Missing token issued at time value');\n        }\n\n        $dayAgo = time() - 86400;\n        $iat = intval($this->payload['iat']);\n        if ($iat > ($now + $skewSeconds) || $iat < $dayAgo) {\n            throw new OidcInvalidTokenException('Token issue at time is not recent or is invalid');\n        }\n\n        // 7. If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.\n        // The meaning and processing of acr Claim Values is out of scope for this document.\n        // NOTE: Not used for our case here. acr is not requested.\n\n        // 8. When a max_age request is made, the Client SHOULD check the auth_time Claim value and request\n        // re-authentication if it determines too much time has elapsed since the last End-User authentication.\n        // NOTE: Not used for our case here. A max_age request is not made.\n\n        // Custom: Ensure the \"sub\" (Subject) Claim exists and has a value.\n        if (empty($this->payload['sub'])) {\n            throw new OidcInvalidTokenException('Missing token subject value');\n        }\n    }\n}\n","sourceCodeStart":55,"sourceCodeEnd":90,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Oidc/OidcIdToken.php#L55-L90","documentation":"After confirming 'iat' exists, validateTokenClaims checks the issued-at time is recent: it must not be newer than now + clock skew, and not older than 24 hours (time() - 86400). This catches replayed/stale tokens and clock drift between client and IdP. Tokens failing this window are rejected.","triggerScenarios":"validate() on a token whose 'iat' is more than 86400 seconds in the past, or in the future beyond the allowed skew ($now + $skewSeconds).","commonSituations":"Server clock skew (VM clock drift, wrong timezone/NTP); IdP and app servers out of sync; cached/old tokens replayed; system clock set far in the future or past; long-lived stored tokens reused after a day.","solutions":["Sync the server clock (enable NTP, e.g. chrony/ntpdate) on both app and IdP hosts","Obtain a fresh ID token instead of reusing a cached one older than 24 hours","Verify the system timezone/date is correct (date -u) and hardware clock is not drifted","If legitimate skew is expected, adjust $skewSeconds passed into the validation"],"exampleFix":"// before: relying on drifted host clock\n$token->validate($now);\n// after: keep host time correct\nsudo timedatectl set-ntp true && timedatectl status; // then re-run validation","handlingStrategy":"validation","validationCode":"$iat = $payload['iat'] ?? null;\n$skew = 300;\nif (!is_numeric($iat) || $iat > time() + $skew || $iat < time() - 86400) { throw new \\RuntimeException('iat out of acceptable window'); }","typeGuard":null,"tryCatchPattern":"try { $token->validate($now, $skewSeconds); } catch (OidcInvalidTokenException $e) { if (str_contains($e->getMessage(), 'not recent')) { /* refresh token / re-authenticate */ } throw $e; }","preventionTips":["Run NTP on app and IdP servers to eliminate clock skew","Never cache or reuse ID tokens longer than the 24h validation window","Request a fresh id_token on each login flow instead of replaying stored ones","Monitor system clock drift and alert on large offsets"],"tags":["oidc","jwt","clock-skew","token-expiry"],"backgroundTag":"jwt-token-expired","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}