
End access across a person’s sessions
Withdraw a person’s existing tokens across devices when they sign out everywhere or a credential needs to be replaced. Wildo checks the revocation state during token use and refresh, rather than waiting only for normal expiry.
Individual-session logout remains a separate action, so ending one session does not have to end all the others.
Example — Respond to a compromised password
A password reset withdraws the person’s previous tokens. Another device cannot keep refreshing the old session after the reset.
For engineers
Use the account-wide revocation path
The authentication controller exposes logoutAll for the current user. Credential-reset services also use the issuer’s account-wide invalidation method. This is its current implementation:
public async invalidateAllTokensForUser(userId: string): Promise<void> {
const nowSeconds = Math.floor(Date.now() / 1000);
const refreshTokenDays = this.appConfigService.config.jwt.refreshTokenExpirationDays;
await this.redisService.set(
`${AUTH_REDIS_KEYS.TOKEN_INVALID_BEFORE}${userId}`,
nowSeconds,
{ ttl: refreshTokenDays * 86400 },
);
this.logDebug('All tokens invalidated for user', { userId, invalidBefore: nowSeconds });
}
The fence expresses which earlier tokens are no longer admissible. The JWT guard, execution-context creation and refresh path consult it. A custom credential-changing flow must use the owning service rather than update the password hash and leave existing sessions untouched.
Choose the scope of logout deliberately
invalidateSession(sessionId) is the sibling for one login session. Account-wide invalidation affects the user’s earlier sessions; per-session invalidation withdraws only the selected one. Cross-tab logout separately clears the current browser’s visible state and client credentials.
Treat the revocation second as part of the boundary
The shared evaluateUserTokenInvalidationFence compares whole-second issuance times with the stored revocation second. It deliberately uses an inclusive comparison:
return { isRevoked: issuedAtSeconds <= invalidBefore, invalidBefore };
A token issued before the boundary is refused, and so is one issued in the same second. A login racing the revocation can therefore obtain a token that is immediately rejected: those timestamps cannot distinguish issuance just before the revocation from issuance just after it. Sign in again after that second has passed; do not relax the comparison or keep retrying the withdrawn token.
| Token issuance time | This timestamp fence’s verdict |
|---|---|
| Before the revocation second | Revoked |
| In the revocation second | Revoked, including a concurrent fresh login |
| After the revocation second | Not revoked by this fence; all other checks still apply |
The atomic refresh publication path repeats the inclusive comparison inside Redis, so a revocation arriving while a successor is being signed is checked again before publication. Timestamp revocation, authorization-version changes and individual-session withdrawal are distinct checks; passing one does not bypass another. Test the equality case alongside an older token and a genuinely later issuance.
Understand the observed result
The next guarded request or refresh with a withdrawn token is refused. This does not reverse requests that already completed. Refresh checks revocation before returning a cached successor, so its short retry window cannot preserve access past a revocation. Keep the distinction between local interface cleanup, one-session logout and account-wide token withdrawal explicit in custom security controls.