{"record":{"id":"ee6fd4029d300180","repo":"passbolt/passbolt_api","slug":"attempt-to-access-an-expired-verify-token","errorCode":null,"errorMessage":"Attempt to access an expired verify token.","messagePattern":"Attempt to access an expired verify token\\.","errorType":"http","errorClass":"ExpiredVerifyTokenAccessException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltCe/JwtAuthentication/src/Service/VerifyToken/VerifyTokenValidationService.php","lineNumber":70,"sourceCode":"     *\n     * @param mixed $verifyTokenExpiry unix timestamp\n     * @return void\n     * @throws \\Passbolt\\JwtAuthentication\\Error\\Exception\\VerifyToken\\InvalidVerifyTokenException if the token is expired.\n     */\n    protected function validateTokenExpiry(mixed $verifyTokenExpiry): void\n    {\n        $maxTokenExpiry = DateTime::now()\n            ->modify('+' . Configure::read(self::VERIFY_TOKEN_EXPIRY_CONFIG_KEY))\n            ->toUnixString();\n        if (\n            !isset($verifyTokenExpiry) ||\n            !is_numeric($verifyTokenExpiry) ||\n            $verifyTokenExpiry > $maxTokenExpiry\n        ) {\n            throw new InvalidVerifyTokenException(__('Invalid verify token expiry.'));\n        }\n        if ($verifyTokenExpiry < time()) {\n            throw new ExpiredVerifyTokenAccessException(\n                __('Attempt to access an expired verify token.')\n            );\n        }\n    }\n\n    /**\n     * Assert verify token is a UUID\n     *\n     * @param mixed $verifyToken token\n     * @return void\n     * @throws \\Passbolt\\JwtAuthentication\\Error\\Exception\\VerifyToken\\InvalidVerifyTokenException if the format is not valid.\n     * @throws \\Cake\\ORM\\Exception\\PersistenceFailedException\n     */\n    protected function validateFormat(mixed $verifyToken): void\n    {\n        if (\n            !isset($verifyToken) ||\n            !is_string($verifyToken) ||","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/JwtAuthentication/src/Service/VerifyToken/VerifyTokenValidationService.php#L52-L88","documentation":"validateTokenExpiry throws ExpiredVerifyTokenAccessException when a verify token's numeric expiry timestamp is earlier than the current time. The token was well-formed and within the max window, but its lifetime has elapsed, so access via this token is refused.","triggerScenarios":"validateToken called with a verify token whose expiry < time() — the user waited past the validity window before completing the verify step, or replayed an old token from a previous session.","commonSituations":"Users abandoning a verification flow and resuming later; tokens generated on a machine with a wrong clock; copying an old verify token link from history/email and reusing it after expiry.","solutions":["Request a new verify token via the proper endpoint and complete verification within its validity window.","Check server clock/NTP; large skew can prematurely expire valid tokens.","Do not reuse verify-token URLs from browser history or old emails.","If the flow routinely expires too fast, increase the server-side verify token TTL configuration."],"exampleFix":"// before\nverifyToken.expiry = Math.floor(Date.now()/1000) + 3600*24*30; // 30 days, may still be stored/resent later\n// after\nif (verifyToken.expiry < Math.floor(Date.now()/1000)) { await requestNewVerifyToken(userId); return; }\nawait validateToken(verifyToken);","handlingStrategy":"validation","validationCode":"if (typeof verifyToken.expiry === 'number' && verifyToken.expiry < Math.floor(Date.now()/1000)) { await requestNewVerifyToken(userId); }","typeGuard":"function isNotExpired(tok: {expiry: number}): boolean {\n  return tok.expiry >= Math.floor(Date.now()/1000);\n}","tryCatchPattern":"try {\n  $validationService->validateToken($verifyToken);\n} catch (ExpiredVerifyTokenAccessException $e) {\n  $newToken = $this->issueNewVerifyToken($userId); // restart the verify flow\n}","preventionTips":["Complete verification flows promptly; don't stash verify-token links for later","NTP-sync server clocks","Never reuse expired tokens from history/emails; request a fresh one","Set a verify token TTL that comfortably exceeds the expected user completion time"],"tags":["verify-token","expired","jwt","authentication"],"backgroundTag":"jwt-token-expired","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}