passbolt/passbolt_api · error · ExpiredRefreshTokenAccessException
Expired refresh token provided.
Error message
Expired refresh token provided.
What it means
throwSecurityExceptionsOnInvalidRefreshToken raises ExpiredRefreshTokenAccessException when the refresh token entity is past its expiry date (isExpired()). Refresh tokens carry a finite lifetime (check_expiry_date on the authentication token); once elapsed the token must not be accepted and the client must re-authenticate.
Solutions
- Re-authenticate (username/password or GPG auth) to mint a fresh token pair; expiry is by design not extendable client-side.
- Refresh tokens proactively before their expiry (e.g. refresh on a schedule well before check_expiry_date).
- Check server clock/NTP sync if tokens expire earlier than expected.
- If the deployment needs longer sessions, increase the refresh token expiry configuration server-side and reissue tokens.
Example fix
// before setInterval(() => refresh(store.refreshToken), 24*3600*1000); // interval longer than token lifetime // after const lifetimeMs = new Date(store.refreshTokenExpiry) - Date.now(); setTimeout(() => refresh(store.refreshToken), Math.max(0, lifetimeMs - 60_000)); // refresh 1 min early
Defensive patterns
Strategy: try-catch
Validate before calling
const expiresAtMs = new Date(storedRefreshTokenExpiry).getTime(); if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now() + 60_000) await reauthenticate(); // refresh before expiry margin
Type guard
function isRefreshable(tok: {token: string, expiry: string|number}): boolean {
const ms = new Date(tok.expiry).getTime();
return Number.isFinite(ms) && ms > Date.now() + 60_000;
} Try / catch
try {
$tokens = $service->renewToken($token, $userId);
} catch (ExpiredRefreshTokenAccessException $e) {
redirectToLogin(); // expired: full re-authentication required
} Prevention
- Schedule refreshes ahead of expiry (e.g. 1-5 min margin)
- Keep server clocks NTP-synced to avoid premature expiry detection
- On session resume, check stored expiry before calling the API
- Increase refresh token TTL in config if users regularly hit the limit
When it happens
Trigger: Presenting a refresh token whose check_expiry_date is in the past — long-lived sessions without activity, tokens issued before an expiry policy change, or clock skew making the token appear expired.
Common situations: A user returning after the refresh token lifetime elapsed; servers with misconfigured timezones/clocks; load balancers with clock drift comparing expiry differently; extending session lifetimes requires re-login.
Related errors
- Attempt to access an expired verify token.
- No active refresh token matching the request could be found.
- No active refresh token matching the request could be found.
- Could not import the user OpenPGP key.
- The domain is invalid. Expected
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/4cc63668a8ded1b6.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/RefreshToken/RefreshTokenAbstractService.php:220
return $refreshToken;
}
/**
* @param \App\Model\Entity\AuthenticationToken $refreshToken Refresh token
* @return void
* @throws \Passbolt\JwtAuthentication\Error\Exception\RefreshToken\ConsumedRefreshTokenAccessException if the token was already consumed
* @throws \Passbolt\JwtAuthentication\Error\Exception\RefreshToken\ExpiredRefreshTokenAccessException if the token is expired
*/
public function throwSecurityExceptionsOnInvalidRefreshToken(AuthenticationToken $refreshToken): void
{
if ($refreshToken->isNotActive()) {
throw new ConsumedRefreshTokenAccessException(
__('The refresh token provided was already used.')
);
}
if ($refreshToken->isExpired()) {
throw new ExpiredRefreshTokenAccessException(
__('Expired refresh token provided.')
);
}
}
}
View on GitHub (pinned to 31c1bbc10f)