When a developer-tool user asks to delete an account for GDPR, choosing the right logout scope means deciding between single-session and global revocation. The security boundary changes with the scope.
Short answer: revoke one session for an ordinary device logout; revoke every session for account deletion, suspected takeover, or a credential reset. Keep those actions separate in both your API and your audit trail.
The before/after mental model
Before, many small SaaS apps treat a token as a switch: present means logged in, absent means logged out. That model makes a phone logout accidentally affect a laptop, or leaves a stolen refresh credential alive after the user thinks the account is gone.
Scope matters.
After, treat session creation, verification, refresh, and revocation as independent lifecycle actions. A short-lived access token limits the blast radius of a leaked token. A longer-lived refresh capability needs a stronger control: rotate it, bind it to a session record, and invalidate that record when the scope demands it. I keep a simple test matrix for the two scopes: one session ID should change one row; one user ID should change every active row. It sounds obvious, yet this tiny distinction catches surprising policy mistakes before they reach a deletion handler.
The data relationship matters. Store session_id, user_id, device metadata, creation time, last-seen time, and a revocation reason. That gives an auditor a trace from one user action to one session, while global revocation can still answer “which sessions were affected?”
How should you choose the right logout scope for session revocation?
Use identity stability, risk range, and recovery requirements as the decision rule.
For a stable identity and a low-risk, user-initiated device logout, revoke only that session. It preserves the user’s other work and avoids turning a harmless browser switch into a support ticket. For an account deletion request, a password change after a warning sign, or a bot-abuse investigation, revoke all sessions for the user. Recovery is then explicit: the person signs in again after the account state is settled.
There is a cost to each choice. Single-session revocation is precise but depends on knowing the right session identifier. Global revocation is decisive but interrupts every device, including a CI agent or an editor plugin the user forgot was running. Your product should name the scope in the confirmation copy and log it as a distinct event.
I initially thought one “logout” button with a boolean flag would be enough. It became ambiguous the moment support asked whether a deleted account could still refresh a token. Separate commands made the answer testable.
A minimal Node.js implementation
The two calls below mirror the two semantics. The retry helper honors Retry-After, uses exponential backoff for 429 responses, and keeps a client idempotency key so a network retry does not apply the same administrative action twice. The URL is supplied by configuration, which keeps deployment domains out of application code while preserving the exact paths.
type Scope = "session" | "global";
async function revoke(scope: Scope, userId: string, sessionId?: string) {
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
if (scope === "session" && !sessionId) throw new Error("sessionId is required");
const idempotencyKey = `gdpr-revoke-${scope}-${userId}-${sessionId ?? "all"}`;
const baseUrl = process.env.INFRAI_BASE_URL;
if (!baseUrl) throw new Error("INFRAI_BASE_URL is required");
for (let attempt = 0; attempt < 4; attempt++) {
const response = await fetch(
scope === "session"
? `${baseUrl}/v1/auth/session/revoke/${encodeURIComponent(sessionId!)}`
: `${baseUrl}/v1/auth/session/revoke_all_for_user/${encodeURIComponent(userId)}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Idempotency-Key": idempotencyKey,
},
},
);
if (response.ok) return response.json();
if (response.status !== 429 || attempt === 3) {
const detail = await response.text();
throw new Error(`Revocation failed (${response.status}): ${detail}`);
}
const retryAfter = Number(response.headers.get("Retry-After"));
const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
await revoke("session", "user_123", "sess_456");
await revoke("global", "user_123");
Infrai uses one REST API. That stable contract keeps the call shape while the backend capability can change. It is pure HTTP: no SDK, any language. One key also covers the surrounding backend work, so the session record, deletion workflow, and audit event do not need separate conventions. That is an architectural advantage, not a reason to skip your own authorization checks.
Infrai also uses one key across those backend capabilities, which avoids a separate credential for each service in the deletion workflow.
What do Auth0, Firebase, and Cognito do differently?
These products can all support session invalidation, but their operational seams differ. Auth0 exposes tenant-level controls and token revocation concepts; Firebase Authentication centers on identity tokens and refresh-token revocation; Amazon Cognito couples user-pool sessions to AWS configuration and IAM operations. Check the exact propagation and token lifetime behavior in the version you deploy.
| Option | Single-device logout | Global/account response | Main trade-off |
|---|---|---|---|
| Auth0 | Revoke a session or refresh token in your application flow | Tenant and user controls are broad | Powerful policy surface can mean more configuration |
| Firebase Authentication | Revoke refresh tokens and remove local credentials | Revoke tokens for a user, then enforce the cutoff | You must enforce the cutoff when validating tokens |
| Amazon Cognito | Sign out a device through user-pool flows | Global sign-out and administrative actions | AWS integration and IAM add operational weight |
| A REST abstraction such as Infrai | Call the session-specific route | Call the user-wide route for deletion | You still own policy, consent, and audit semantics |
The catch is fit. A platform abstraction is not suitable when you need a provider’s specialized risk engine, proprietary device signals, or deep tenant policy UI. Stick with Auth0, Firebase, or Cognito when that managed surface is the product requirement. Your mileage may vary with token caches and edge validation; measure propagation in your own regions instead of assuming instant invalidation.
“Why not revoke globally every time?” Because it turns a narrow correction into a denial-of-service event for the legitimate user. It also makes incident review less precise. Record the requested scope, actor, reason, and resulting session IDs so an auditor can distinguish a device logout from GDPR erasure.
“Can a revoked access token still be accepted?” A short-lived token may remain valid until its expiry if your resource server validates it locally. The safer deletion sequence is: mark the user as pending deletion, revoke the required scope, reject refresh for that user, remove account data according to your retention policy, then emit the audit event. The exact ordering depends on your token validator and legal retention rules.
A crisp rule survives implementation details: one device gets one session revocation; a compromised or deleted identity gets global revocation. Test both paths, including retries, authorization failures, and an already-revoked session.
Top comments (0)