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
- 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.
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
- 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
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
- Expired refresh token provided.
- Could not import the user OpenPGP key.
- Invalid verify token expiry.
- No active refresh token matching the request could be found.
- No active refresh token matching the request could be found.
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)