Implementing Sign Out Everywhere Across Devices

“Sign out of all devices” is the button users press when they think something is wrong, which makes it the worst possible place for a partial implementation. This page is part of the secure logout and session termination guide and covers what the operation must reach, in what order, and how to prove it worked.

Everything the Operation Has to Reach

A user’s access is spread across more places than a session table. Missing any one of them means the button reassures without protecting.

What sign-out-everywhere must invalidate Server-side session records, refresh token families, any self-contained tokens still in flight, cached authorization decisions, and long-lived connections such as WebSockets all have to be ended for the operation to be complete. Session records delete every one Refresh families revoke upstream Cached decisions bump the version Tokens in flight expire or deny-list Live connections close WebSockets
The bottom row is what teams forget: an open WebSocket authenticated an hour ago keeps streaming until something closes it.

The Implementation

Work from the per-user session index, and treat the version counter as the mechanism that reaches everything you cannot enumerate.

export async function signOutEverywhere(userId: string, opts: { keepCurrent?: string } = {}) {
  const sids = await sessions.listForUser(userId);
  const doomed = sids.filter((sid) => sid !== opts.keepCurrent);

  await sessions.destroyMany(doomed);                 // 1 · server-side records
  await refreshTokens.revokeAllFamilies(userId);      // 2 · upstream credentials
  await users.incrementSessionVersion(userId);        // 3 · anything cached or self-contained
  await realtime.closeConnectionsFor(userId, doomed); // 4 · long-lived connections
  await audit.record("sessions.revoked_all", { userId, count: doomed.length });
  await notify.securityEvent(userId, "signed_out_everywhere", { count: doomed.length });

  return doomed.length;
}

The version counter is what makes this reliable. Any component holding a cached authorization decision or a short-lived token derived from the session compares the version it saw against the current one; a mismatch is a miss, with no broadcast and no cache flush. It is the same technique used for caching authorization decisions, and it is the difference between “we deleted the rows” and “access actually stopped”.

Decide deliberately whether the current session survives. Keeping it is friendlier — the user stays logged in on the device they are using — and is right when the trigger is a password change. Ending everything including the current session is right when the trigger is a suspected compromise, because the person pressing the button may not be the account owner.

Handling the Awkward Cases

Two categories resist enumeration and need their own handling.

Self-contained access tokens already issued cannot be recalled. If their lifetime is short — the five to fifteen minutes recommended in the refresh and rotation guide — the residual window is acceptable and expiry does the work. If it is longer, you need either a deny list consulted by resource servers or a version check on each request, and both are lookups you should have decided about before this feature was requested.

Long-lived connections need an explicit close. Keep a registry of active WebSocket and server-sent-event connections keyed by user, close them when sessions are revoked, and have the client treat an unexpected close as a signal to re-authenticate rather than to reconnect blindly. A connection that reconnects with a dead cookie will fail cleanly; one that reconnects with a cached token may not.

Residual access after sign-out-everywhere Session-based access stops immediately, cached decisions stop at the next version check, and any self-contained access token remains valid until it expires unless a deny list is consulted. Sessions immediate Cached decisions next version check Access tokens until exp button pressed
The bottom bar is your real revocation latency. If it is longer than you are comfortable saying out loud, shorten the token lifetime rather than explaining the window.

Making It Verifiable

Assert the outcome rather than the mechanism. In an integration test, create three sessions for one user, call the operation, then confirm that all three cookies return unauthenticated, that a refresh attempt with each family’s token fails, and that a request carrying a previously cached authorization decision is re-evaluated. Add a timing assertion — the last successful request using any revoked credential must precede the revocation — because that interval is the number an incident review will ask about.

Show the user the result. “Signed out of 4 devices” with a list of what they were is both a confirmation and a detection mechanism: a device the user does not recognise in that list is exactly what the feature exists to surface.

When the Operation Should Run Automatically

Users press the button rarely; the system should press it for them more often. Four events warrant an automatic sign-out everywhere, and each needs a decision recorded in code rather than left to convention.

A password change is the clearest. The usual motivation is suspicion that the old password is known, so leaving other sessions alive defeats the purpose entirely. Keep the current session, end everything else, and tell the user how many were affected.

Removing or replacing a second factor is the same argument one layer up: the authentication path that established those sessions no longer exists, so the sessions established through it should not either. The same applies when a passkey is deleted, because the credential that proved identity has been withdrawn.

A detected refresh-token reuse — the alarm described in detecting refresh token reuse with rotation — should revoke the family and end the associated sessions immediately. You cannot distinguish the attacker from the user at that moment, so ending both and forcing re-authentication is the safe resolution.

Finally, an administrator disabling or suspending an account must end everything at once. A disabled account whose existing sessions keep working is a control that exists on paper only, and it is the exact scenario an auditor will ask you to demonstrate.

Performance at Scale

For most accounts this operation touches a handful of records and completes in milliseconds. For a service account, a shared login, or an account under attack it can touch thousands, and the naive implementation — read all identifiers, delete keys one at a time, revoke each family individually — turns into a long-running request that times out halfway through and leaves the account in a partially revoked state.

Batch the work. Delete session keys in chunks, revoke families in bulk where your provider supports it, and make the whole operation idempotent so a retry after a timeout completes rather than restarting. Bump the version counter first rather than last: it is a single write, it immediately invalidates cached decisions and version-checked tokens, and it means a partial failure still leaves access curtailed rather than intact.

For genuinely large accounts, run the enumeration asynchronously and return promptly with a confirmation that the revocation is in progress, while the version bump has already taken effect synchronously. The user gets a fast, honest response and the guarantee lands immediately, with the slower cleanup following behind.

Return the count of sessions ended synchronously even when the cleanup continues in the background, because that number is what the user is actually asking for when they press the button, and producing it later leaves the confirmation screen with nothing useful to say.

Keep the asynchronous path observable: a queue depth that grows during an incident tells you the revocation is lagging, which is a fact you want on a dashboard rather than discovered afterwards.

Frequently Asked Questions

Should the current session be kept?

It depends on the trigger. After a password change, keeping the current session is friendlier and safe, because the person is authenticated and acting deliberately. After a suspected compromise or from an administrator’s action, end everything: you cannot be sure the current session belongs to the account owner. Offer both and label them clearly rather than picking one silently.

How do I revoke access tokens that were already issued?

You cannot recall them, so you either wait for expiry or make resource servers consult something. The cheapest option is a per-user version claim compared against a cached counter — one small lookup that turns every issued token into a revocable one. A deny list keyed by token identifier also works but grows with traffic and needs its own expiry. Short lifetimes remain the simplest answer for most systems.

What should the user be told?

Exactly what happened and where: the number of sessions ended and, where you can, what the devices were. Send the notification through a channel that does not depend on the sessions just revoked, and include the time and source of the action. That message is the one that turns “my account behaved strangely” into a report you can act on.

Does this need to be rate-limited?

Yes, lightly. It is an expensive operation touching several systems, and a script calling it in a loop produces load and a stream of notifications that trains the user to ignore them. A handful of invocations per hour per account is more than any legitimate use needs, and exceeding it is itself worth logging.

Should administrators be able to run it for another user?

Yes — it is a core incident-response action — with the actor recorded in the audit trail and the account owner notified. What it must not do is silently reuse the same code path as the user-initiated version, because the two have different notification and approval requirements. Make the administrative variant explicit, and require a reason that lands in the log next to it.

Triggers That Should Invoke It Automatically

Events that should sign a user out everywhere A password change, removal of a second factor, detected refresh token reuse, and an administrator disabling the account should each revoke every session for that user. Password change keep current end the rest Factor removed the auth path no longer exists Reuse detected end everything including current Account disabled immediate, audited
The last column is the one auditors ask to see demonstrated: a disabled account whose sessions keep working is a control in name only.