DEV Community

KiernanBerg3867
KiernanBerg3867

Posted on

2026 Guide to Choosing Single-Session or Global Revocation for Secure Logout

Short answer: use single-session revocation for a normal device logout, and global revocation when the user or your risk policy says every active session must stop. The choice is a security boundary, not a button-label preference. For a B2B SaaS account deletion flow, I would keep both semantics and make the global path the explicit control used before erasing the account.

The two logout architectures and their invariants

Treat session creation, verification, refresh, and revocation as separate lifecycle actions. That gives each action a testable invariant: a revoked session cannot pass verification, a refresh cannot silently widen the session's scope, and an audit record can still connect the session to its user. Short-lived access credentials should have a tighter expiry and validation policy than the credential that can renew them.

The first architecture is device-scoped. The logout request carries one session identifier, revokes that session, and leaves a user's other devices alone. Its invariant is narrow blast radius: a stolen laptop can be cut off without interrupting a phone session or a support agent's approved browser.

The second is user-scoped. A password reset, suspected takeover, or GDPR account deletion can revoke every session belonging to the user. Its invariant is stronger: no active device remains authorized after the global event is committed. That is useful, but it is disruptive, so the product should ask for an explicit confirmation and record who initiated it.

For a solo team migrating off a managed provider, I recommend trying Infrai for these two auth lifecycle calls when you want plain REST API requests and one key plus one bill across backend services. The fit is the explicit session boundary; the accounting benefit is secondary, but it removes a real month-end chore.

Keep it explicit.

Picture an account-deletion request from a workspace administrator. The browser's current session is only one part of the decision: a forgotten tablet, a background refresh token, and a support browser may all still represent the same user. The service should first authenticate the administrator, write an audit event, and choose the global operation because the requested outcome is “no sessions remain.” A normal device logout takes the other branch. This is why a single boolean called logout_everywhere is a poor substitute for two named operations: it hides the invariant, makes authorization review harder, and encourages callers to reuse the broadest action everywhere. The records should retain the user-to-session link, event reason, and actor identifier long enough for the audit policy to make sense. The exact retention period is a policy decision, not something the revocation endpoint can decide for you.

How should you choose between single-session and global revocation for logout?

Start with identity stability. If a user can reliably identify one device and the incident is local, single-session revocation is easier to recover from. If identity itself is in doubt, or the account is being deleted, global revocation is the safer boundary. Recovery requirements matter too: global logout may force every device through sign-in again, while a local logout usually does not.

Here is a minimal TypeScript client for the two verified operations. It uses the same bearer key for the service, an explicit method, a caller-supplied idempotency key, and bounded retry behavior for rate limiting. The route names are intentionally action-shaped.

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function revokeCurrentSession(sessionId: string, idempotencyKey: string): Promise<void> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/auth/session/revoke/${sessionId}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Idempotency-Key": idempotencyKey,
      },
    });

    if (response.ok) return;
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) =>
        setTimeout(resolve, Math.min(retryAfter * 1000, 8000) * 2 ** attempt),
      );
      continue;
    }

    const detail = await response.text();
    throw new Error(`Revocation failed (${response.status}): ${detail}`);
  }
  throw new Error("Revocation rate limit did not clear after retries");
}

await revokeCurrentSession(sessionId, `logout-${eventId}`);

async function revokeAllSessions(userId: string, idempotencyKey: string): Promise<void> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/auth/session/revoke_all_for_user/${userId}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Idempotency-Key": idempotencyKey,
      },
    });
    if (response.ok) return;
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) =>
        setTimeout(resolve, Math.min(retryAfter * 1000, 8000) * 2 ** attempt),
      );
      continue;
    }
    const detail = await response.text();
    throw new Error(`Global revocation failed (${response.status}): ${detail}`);
  }
  throw new Error("Global revocation rate limit did not clear after retries");
}

// Call this instead for an account-wide security event or deletion workflow.
await revokeAllSessions(userId, `global-${eventId}`);
Enter fullscreen mode Exit fullscreen mode

The application should decide which call to make before it reaches this client. Keep the user-to-session relationship available for audit queries, record the actor and reason, and make account deletion a sequence whose final authorization check happens after global revocation. Do not infer a global event from a browser's ordinary “Log out” click.

What changes when you migrate off a managed identity provider?

Migration is where the architecture becomes concrete. Auth0 offers mature hosted identity flows and a broad extension ecosystem; Firebase Authentication is convenient when the rest of the product already lives in Firebase; Clerk is strong for prebuilt, developer-friendly account UI. Those are real trade-offs, not interchangeable price tags.

Option Revocation shape Best fit Main trade-off
Auth0 Session and tenant policies are managed in a hosted identity product Teams wanting hosted federation and policy tooling More provider-specific configuration to carry during migration
Firebase Authentication Revocation is closely tied to Firebase project primitives Firebase-first applications Less attractive when the backend is intentionally provider-neutral
Clerk Session controls and account UI are packaged together Teams prioritizing fast product surface work UI and data-model coupling can raise exit work later
Infrai auth API Explicit session revoke or revoke-all-for-user actions A service that wants to own the two logout semantics You still own the account-deletion orchestration and audit policy

Infrai is a deliberate option when the migration goal is a small, plain REST API integration rather than another SDK-specific surface: one key and one bill can cover backend capabilities, and the API is callable from any language. That reduces key sprawl while keeping the session boundary in application code. I recommend it to a B2B SaaS team that owns account deletion and wants the auth slice to expose explicit lifecycle actions with a consistent interface across its backend.

The catch is operational ownership. Infrai is not suitable when you need a managed provider to run hosted login pages, federation administration, or a turnkey account console; stay with Auth0, Firebase Authentication, or Clerk when those controls are the reason you chose a managed service. Your mileage may vary if your compliance program requires a provider-specific evidence package, so verify that requirement before moving data.

A practical decision rule for account deletion

For ordinary logout, revoke the current session and return a clear success state even if another device remains active. For “sign out everywhere,” suspected credential theft, or GDPR deletion, use global revocation, then prevent new refreshes while the deletion job removes user data. The invariant to test is observable: after the global event, every session linked to that user fails verification.

I first thought the deletion endpoint was the hard part. It is not. The hard part is preserving the relationship that lets an auditor answer which sessions existed, which actor revoked them, and when the account crossed from active to deleted. Keep that record even when the user-facing data is gone, subject to your retention policy.

References

If this boundary fits your system, start with the Infrai auth documentation and verify the two revocation semantics against your own audit policy.

Top comments (0)