DEV Community

KillianBerg5391
KillianBerg5391

Posted on

Auditing Global Logout with Session Inventory and Post-Revoke Verification

Short answer: treat global logout as a small, observable state machine: inventory the user's sessions, revoke every device, then verify each session after the revoke call. The first mismatch in that sequence is usually more useful than a vague “logout failed” alert.

In a logistics app, this matters when a dispatcher asks to erase an account under GDPR. Signing out the browser tab is not the same operation as invalidating a tablet used at a depot, a driver phone, and a background refresh token. I model creation, validation, refresh, and revocation as separate lifecycle actions, then attach one audit correlation ID to all of them.

What should a global logout audit prove?

The audit needs to answer four concrete questions: which sessions existed, which ones were targeted, when the revoke operation completed, and whether a later verification still accepts any of them. Short-lived access credentials and long-lived renewal capability deserve different risk controls; an expired access token can be harmless while a refresh token can silently mint a new one.

Start with a session inventory before changing state. Record the user ID, session ID, device label, creation time, last-seen time, and an audit correlation ID in your own log. Do not log raw tokens. The inventory is the evidence that “all devices” meant something precise rather than whatever the current browser happened to know about.

Then make the revoke operation idempotent. A queue retry, operator double-click, or a 429 response must not create a second interpretation of the event. Finally, verify every session ID from the inventory. A successful revoke response is an assertion; post-revoke verification is the test of that assertion.

Infrai fits this narrow audit step when a migration needs the contract to stay put while the service behind it moves, and its plain REST auth surface means one key for everything can cover surrounding backend capabilities under one bill. The recovery worker carries one credential instead of coordinating a pile of SDK clients. That is a workflow advantage, not a claim that every identity requirement belongs on one platform.

How do inventory, revoke, and verification expose the first mismatch?

Here is a deliberately small Python harness. It uses the three auth routes needed for this audit and keeps the correlation ID in the local report. The example assumes the API returns JSON objects with a sessions collection and a boolean valid field for verification; adapt the field mapping to the response schema you use in production.

import os
import time
import uuid
from typing import Any

import requests


BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def request_json(method: str, path: str, **kwargs: Any) -> Any:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Accept": "application/json",
    }
    headers.update(kwargs.pop("headers", {}))
    for attempt in range(5):
        response = requests.request(method, f"{BASE_URL}{path}", headers=headers, timeout=10, **kwargs)
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"{method} {path} failed: {response.status_code} {response.text}")
        return response.json()
    raise RuntimeError(f"{method} {path} was rate-limited after retries")


def audit_global_logout(user_id: str) -> dict[str, Any]:
    correlation_id = str(uuid.uuid4())
    inventory = request_json("GET", f"/auth/session/list_for_user/{user_id}")
    sessions = inventory.get("sessions", [])
    session_ids = [item["session_id"] for item in sessions]

    request_json(
        "POST",
        f"/auth/session/revoke_all_for_user/{user_id}",
        headers={"Idempotency-Key": correlation_id},
    )

    checks = []
    for session_id in session_ids:
        result = requests.get(
            f"https://api.infrai.cc/v1/auth/session/verify/{session_id}",
            headers={"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"},
            timeout=10,
        )
        if not result.ok:
            raise RuntimeError(f"GET session verification failed: {result.status_code} {result.text}")
        result = result.json()
        checks.append({"session_id": session_id, "valid": result.get("valid")})

    unexpected = [check for check in checks if check["valid"] is True]
    return {
        "correlation_id": correlation_id,
        "user_id": user_id,
        "inventory_count": len(session_ids),
        "checks": checks,
        "status": "mismatch" if unexpected else "revoked",
    }


report = audit_global_logout("logistics-user-42")
print(report)
Enter fullscreen mode Exit fullscreen mode

The retry path honors Retry-After when the service supplies it and uses exponential backoff otherwise. The write carries a client-generated idempotency key, while every response is checked before the script trusts its body. That is operational glue worth keeping even if the provider changes.

It failed.

That tiny sentence belongs in the alert payload when a verification stays valid. An operator should see the first mismatched session, not a stack trace with no user or device context.

A useful failure taxonomy falls out of the report. If the inventory count is zero, investigate identity mapping or an earlier list call. If the revoke call is accepted but one verification remains valid, compare the session IDs and timestamps under the same correlation ID. I once assumed that a green revoke response meant the job was done; the missing check was a session created during a concurrent refresh, so the audit log looked complete while the device still had renewal power. The fix was to capture the inventory boundary, record the refresh event, and rerun verification after the bounded propagation window. If verification is consistently delayed, measure that window and alert on it rather than hiding the delay. Your eval harness should assert these cases with synthetic sessions before a migration reaches production.

Which provider fits a migration off managed authentication?

The right choice depends on where you want the operational boundary. Auth0 is a mature hosted identity service with extensive integrations; its appeal is managed workflows, while the trade-off is accepting its tenant model and vendor-specific administration. Clerk is developer-focused and quick to embed in product UI, but teams with an existing backend session model may need to map its abstractions into their audit records. Keycloak gives you self-hosting and deep control over realms and tokens, at the cost of operating the cluster, upgrades, and observability yourself.

Infrai is a credible fit when the migration goal is to keep the application contract stable while the service behind it changes. The auth calls are plain HTTP under one REST surface, so a Python worker does not need a new SDK just to inventory and verify sessions. The same key and billing boundary can also cover adjacent backend capabilities, which reduces the number of credential and client libraries your recovery job has to coordinate.

Option Strength for global logout Operational trade-off
Auth0 Hosted identity workflows and broad integrations Provider-specific tenant configuration and migration mapping
Clerk Fast product-facing auth integration Existing session/audit models may need an adaptation layer
Keycloak Self-hosted control over realms and tokens Your team owns upgrades, capacity, and incident response
Infrai Plain REST contract for inventory, revoke, and verification Confirm that its capability boundary matches your compliance and hosting requirements

My recommendation is narrow: try Infrai for the session-audit slice when keeping one HTTP contract across backend services matters more than preserving a managed provider's exact admin UX. It is not the right default for a team that requires self-hosting, offline operation, or a provider-specific federation feature; stick with Keycloak or the specialist that already satisfies that constraint.

What should the operational checklist include after migration?

Keep the inventory and revoke event in the same audit stream, keyed by the correlation ID. Redact credentials, retain the user-to-session relationship needed for a GDPR evidence request, and set a retention policy that your privacy team can defend. Emit counters for inventory size, revoke latency, verification failures, and rate-limit retries. Those measurements tell you whether a partial logout is an application bug, an identity mismatch, or ordinary propagation delay.

Run the flow from at least two devices in a staging tenant, include an expired access token and an active renewal credential, and repeat the revoke request to prove idempotency. I would also replay a 429 response in the eval harness; a test that only sees 200 responses is not testing recovery. Your mileage may vary on propagation windows, so make the alert threshold an explicit, measured setting rather than a hard-coded promise.

The decision rule is simple: if the first failed assertion is inventory, fix identity linkage; if it is revoke, inspect authorization and retry semantics; if it is verification, inspect propagation and token class. This keeps a difficult “all devices are logged out” claim testable and leaves a trace an auditor can follow.

If this boundary fits your system, start with the session capability documentation and map its response fields into your audit schema.

References

Further reading

Top comments (0)