passbolt/passbolt_api · error · ExpiredVerifyTokenAccessException

Attempt to access an expired verify token.

Error message

Attempt to access an expired verify token.

What it means

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.

Solutions

  1. Request a new verify token via the proper endpoint and complete verification within its validity window.
  2. Check server clock/NTP; large skew can prematurely expire valid tokens.
  3. Do not reuse verify-token URLs from browser history or old emails.
  4. If the flow routinely expires too fast, increase the server-side verify token TTL configuration.

Example fix

// before
verifyToken.expiry = Math.floor(Date.now()/1000) + 3600*24*30; // 30 days, may still be stored/resent later
// after
if (verifyToken.expiry < Math.floor(Date.now()/1000)) { await requestNewVerifyToken(userId); return; }
await validateToken(verifyToken);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof verifyToken.expiry === 'number' && verifyToken.expiry < Math.floor(Date.now()/1000)) { await requestNewVerifyToken(userId); }

Type guard

function isNotExpired(tok: {expiry: number}): boolean {
  return tok.expiry >= Math.floor(Date.now()/1000);
}

Try / catch

try {
  $validationService->validateToken($verifyToken);
} catch (ExpiredVerifyTokenAccessException $e) {
  $newToken = $this->issueNewVerifyToken($userId); // restart the verify flow
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/ee6fd4029d300180. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/VerifyToken/VerifyTokenValidationService.php:70

     *
     * @param mixed $verifyTokenExpiry unix timestamp
     * @return void
     * @throws \Passbolt\JwtAuthentication\Error\Exception\VerifyToken\InvalidVerifyTokenException if the token is expired.
     */
    protected function validateTokenExpiry(mixed $verifyTokenExpiry): void
    {
        $maxTokenExpiry = DateTime::now()
            ->modify('+' . Configure::read(self::VERIFY_TOKEN_EXPIRY_CONFIG_KEY))
            ->toUnixString();
        if (
            !isset($verifyTokenExpiry) ||
            !is_numeric($verifyTokenExpiry) ||
            $verifyTokenExpiry > $maxTokenExpiry
        ) {
            throw new InvalidVerifyTokenException(__('Invalid verify token expiry.'));
        }
        if ($verifyTokenExpiry < time()) {
            throw new ExpiredVerifyTokenAccessException(
                __('Attempt to access an expired verify token.')
            );
        }
    }

    /**
     * Assert verify token is a UUID
     *
     * @param mixed $verifyToken token
     * @return void
     * @throws \Passbolt\JwtAuthentication\Error\Exception\VerifyToken\InvalidVerifyTokenException if the format is not valid.
     * @throws \Cake\ORM\Exception\PersistenceFailedException
     */
    protected function validateFormat(mixed $verifyToken): void
    {
        if (
            !isset($verifyToken) ||
            !is_string($verifyToken) ||

View on GitHub (pinned to 31c1bbc10f)