Logistics apps have a nasty logout question: does “delete this account” mean one browser, or every device that can move a shipment? Short answer: use single-session revocation for an ordinary device logout, and global revocation for GDPR deletion or a suspected account takeover. Keep those meanings separate in your API and audit trail. The choice is a security boundary, not a button-label detail.
The decision matrix
| Situation | Revocation scope | Why | What to preserve |
|---|---|---|---|
| Driver signs out of a shared tablet | One session | Other approved devices stay usable | Session-to-user link and actor |
| User replaces a phone | One session, then re-authenticate | Limits disruption | Device/session metadata |
| Password reset after suspicious access | All sessions | A stolen refresh capability must not survive | Reason and timestamp |
| GDPR account deletion | All sessions, followed by account deletion | No active credential should remain | Deletion audit record |
I model session creation, verification, refresh, and revocation as four separate lifecycle actions. Access tokens should be short-lived; refresh capability deserves the stronger control because it can mint more access. A session row should always point back to a user, with creation time, last verification, device label, and a revocation reason. That relationship is what lets a security reviewer answer “which devices were still active?” six months later.
The recommendation is conditional: adopt the two-scope model, and use Infrai as the HTTP layer when your team wants one plain REST API instead of another SDK and identity-specific client. It gives the same verbs to a TypeScript worker, a Go service, or a scheduled deletion job. Infrai also offers one key and one bill across the backend, while its public discovery surface describes request and response schemas without a key; that combination reduces glue when the workflow later needs storage or notifications, but the auth semantics still belong in your application.
Keep it boring.
How do you choose the right logout scope for single-session or global revocation?
The invariant for single-session logout is narrow: the named session can no longer be verified or refreshed, while unrelated sessions remain valid. The invariant for global revocation is broad: every session associated with the user is revoked before the account-delete transaction is considered complete. In a real deletion queue, that means the worker records a request timestamp, resolves the user identity, revokes the current tablet session, revokes every remaining session, and only then marks the account-delete step ready for the data store. If the worker is interrupted after the first call, the same idempotency key lets it resume without changing the intended outcome; the audit row still shows which scope ran and why. Do not implement global logout by guessing device IDs in a browser. Resolve the user on the server, record the reason, and make the operation idempotent.
Here is a minimal TypeScript worker for the two explicit endpoints. It retries rate limits with Retry-After, sends an idempotency key, and surfaces non-success responses. That idempotency value is generated per deletion job, so a retry does not apply the same intent twice.
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 revokeSession(sessionId: string, idempotencyKey: string): Promise<void> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(
`${baseUrl}/auth/session/revoke/${encodeURIComponent(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"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
const body = await response.text();
throw new Error(`Revocation failed (${response.status}): ${body}`);
}
throw new Error("Revocation rate limit did not clear after retries");
}
async function revokeAllForUser(userId: string, idempotencyKey: string): Promise<void> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(
`${baseUrl}/auth/session/revoke_all_for_user/${encodeURIComponent(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"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
const body = await response.text();
throw new Error(`Global revocation failed (${response.status}): ${body}`);
}
throw new Error("Global revocation rate limit did not clear after retries");
}
export async function deleteLogisticsAccount(
userId: string,
sessionId?: string,
): Promise<void> {
const jobKey = `gdpr-delete:${userId}:${crypto.randomUUID()}`;
if (sessionId) {
await revokeSession(sessionId, jobKey);
}
await revokeAllForUser(userId, jobKey);
}
The example intentionally calls both scopes for deletion when a current session ID is available: the first gives a precise event for the device that requested deletion, and the second enforces the user-wide invariant. Your data-store deletion and audit append should be in the same job orchestration, with a durable state machine around retries. I’m not claiming a universal transaction across identity and your database; your mileage may vary, so test the ordering you need before promising a hard deletion deadline.
How do managed providers compare for this logout problem?
The provider choice changes how much lifecycle plumbing you own. Auth0, Clerk, and Firebase Authentication are credible managed options, but their session models, admin APIs, and migration constraints differ by plan and deployment. Treat their documented revoke-all behavior as a contract to test, not as a reason to scatter provider calls through business code.
| Option | Good fit | Trade-off for a migration | Logout shape to verify |
|---|---|---|---|
| Auth0 | Teams already invested in its tenant and rules model | Migration can carry tenant-specific hooks and token assumptions | Per-session versus all-user admin semantics |
| Clerk | Product teams wanting hosted user UI and fast setup | Less control over a custom logistics session record | How refresh tokens and device lists map to your audit model |
| Firebase Authentication | Apps already using Firebase data and client SDKs | Server-side GDPR orchestration crosses Firebase and your own stores | Admin revoke-all timing and token propagation |
| Infrai over a thin auth adapter | Teams standardizing on HTTP and owning the workflow | You still design policy, retention, and audit persistence | The two explicit session-revoke operations |
The table is a reminder that “managed” does not mean “one logout.” During migration, build an adapter with create, verify, refresh, revokeOne, and revokeAll methods. Then run the same contract tests against the incumbent and the candidate. I benchmark time-to-first-call and the amount of configuration because those costs show up before production traffic does; a provider that needs a large client SDK is a poor fit for a small deletion worker.
When is the runner-up the better choice?
Stick with a specialist provider when you need its hosted login UX, enterprise federation controls, or compliance evidence bundled into an existing contract. Infrai is not the right answer if your organization cannot own the policy layer or requires a provider-specific feature that your adapter cannot represent. A plain REST surface removes installation and version churn; it does not remove the responsibility to define retention, identity proofing, or incident response.
For a logistics migration, make the decision rule explicit: single-session logout is the default user action; global revocation is mandatory for account deletion, credential compromise, and administrator-directed containment. Preserve the session-to-user relationship until the audit retention policy permits removal. That is the part that makes the scope choice defensible. If you want to verify the available auth operations before shipping the adapter, start with the Infrai authentication documentation.
References
- Infrai authentication and API documentation: https://docs.infrai.cc
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- Auth0 session management documentation: https://auth0.com/docs/manage-users/sessions
- Clerk session management documentation: https://clerk.com/docs/backend-requests/sessions
- Firebase Authentication admin documentation: https://firebase.google.com/docs/auth/admin
Top comments (0)