{"record":{"id":"7de3e527ead8c03a","repo":"BookStackApp/BookStack","slug":"missing-token-issued-at-time-value","errorCode":null,"errorMessage":"Missing token issued at time value","messagePattern":"Missing token issued at time value","errorType":"exception","errorClass":"OidcInvalidTokenException","httpStatus":null,"severity":"error","filePath":"app/Access/Oidc/OidcIdToken.php","lineNumber":67,"sourceCode":"        }\n\n        // 5. The current time MUST be before the time represented by the exp Claim\n        // (possibly allowing for some small leeway to account for clock skew).\n        if (empty($this->payload['exp'])) {\n            throw new OidcInvalidTokenException('Missing token expiration time value');\n        }\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'])) {","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Access/Oidc/OidcIdToken.php#L49-L85","documentation":"OidcIdToken::validateTokenClaims performs OIDC ID token claim validation step 6: the 'iat' (issued-at) claim must be present to reject tokens issued too far from current time and limit nonce storage windows. When the decoded token payload has an empty or missing 'iat' claim, this exception is thrown. It indicates the identity provider issued a non-conformant ID token.","triggerScenarios":"Calling validate() on an OidcIdToken whose decoded JWT payload lacks the 'iat' claim or has it set to 0/empty (e.g. empty($this->payload['iat']) evaluates true).","commonSituations":"Misconfigured or minimal IdP implementations that omit 'iat'; hand-crafted or test tokens missing claims; tokens mangled by proxies that strip claims; using a token endpoint response whose id_token was built by custom middleware.","solutions":["Fix the identity provider / token issuer so the ID token includes a valid NumericDate 'iat' claim","Check that the JWT is not being decoded/filtered in a way that drops claims before validation","If generating tokens in tests, add 'iat' => time() to the payload","If you cannot fix the IdP, pre-decode the token and reject/warn before calling validate()"],"exampleFix":"// before (test token payload)\n['iss' => $iss, 'aud' => $aud, 'sub' => $sub, 'exp' => time()+3600]\n// after\n['iss' => $iss, 'aud' => $aud, 'sub' => $sub, 'exp' => time()+3600, 'iat' => time()]","handlingStrategy":"validation","validationCode":"$payload = json_decode(base64_decode(str_replace('-', '+', str_replace('_', '/', explode('.', $idToken)[1]))), true);\nif (empty($payload['iat'])) { throw new \\RuntimeException('ID token missing iat claim'); }","typeGuard":null,"tryCatchPattern":"try { $token->validate($now); } catch (OidcInvalidTokenException $e) { if ($e->getMessage() === 'Missing token issued at time value') { /* reject token / alert on IdP conformance */ } throw $e; }","preventionTips":["Pre-decode and sanity-check required claims (iss, aud, exp, iat, sub) before calling validate()","Add IdP conformance tests that assert iat is present","Log the full payload (excluding PII) when validation fails to diagnose issuer issues"],"tags":["oidc","jwt","token-validation"],"backgroundTag":"jwt-missing-claim","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}