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

  1. Re-authenticate (username/password or GPG auth) to mint a fresh token pair; expiry is by design not extendable client-side.
  2. Refresh tokens proactively before their expiry (e.g. refresh on a schedule well before check_expiry_date).
  3. Check server clock/NTP sync if tokens expire earlier than expected.
  4. 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

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


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)