Short answer: verify logout by walking the session lifecycle in order—create, validate, refresh, revoke—and correlate each result to the same user and session record. If a revoked session still reaches a protected endpoint, the first mismatch is usually between the credential being presented and the session identifier being revoked, not the logout button itself.
In a healthtech system, that distinction matters. A clinician may sign out a shared workstation while a stolen browser token remains usable elsewhere. Session security has to win that race without turning every normal refresh into a support ticket.
The button is not evidence.
Start with the lifecycle, not the logout screen
Treat these as separate operations: session creation establishes a record, verification checks whether that record is usable, refresh extends a short-lived access credential, and revocation changes the record's state. Logging a client-side redirect only proves that the UI navigated away.
I map the request to a session ID and user ID before inspecting any timestamps. Then I ask four narrow questions: did creation return the ID I later revoke; did verification read that exact ID; did refresh mint a new credential after revocation; and did the protected service consult the current session state instead of trusting an old access token? The first “no” is the useful failure boundary. It fails fast.
Keep access and renewal risk separate. A short-lived access token limits the window for a copied header, while a refresh token deserves rotation and immediate invalidation when its session is revoked. A refresh that succeeds after the revoke event is a security finding even if an already-issued access token has not expired yet.
One practical trap: teams often send “logout all devices” when the user meant “this device.” Those are different security and support semantics. The former should invalidate every session associated with the user; the latter should target one session ID and leave the user's other clinical devices alone.
What should you verify when a revoked session appears active?
The verification endpoint gives the investigation a stable checkpoint. The revoke endpoint is a state transition. Use both, and preserve the response plus a request timestamp in the audit trail.
import os
import time
import requests
BASE_URL = os.environ["AUTH_BASE_URL"].rstrip("/")
API_KEY = os.environ["INFRAI_API_KEY"]
SESSION_ID = os.environ["SESSION_ID"]
headers = {"Authorization": f"Bearer {API_KEY}"}
def call(method, path, payload=None):
for attempt in range(4):
response = requests.request(method, BASE_URL + path, headers=headers, json=payload, timeout=10)
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
response.raise_for_status()
return response.json()
raise RuntimeError("rate limit persisted after retries")
before = call("GET", f"/v1/auth/session/verify/{SESSION_ID}")
revoked = call("POST", f"/v1/auth/session/revoke/{SESSION_ID}")
after = call("GET", f"/v1/auth/session/verify/{SESSION_ID}")
print({"before": before, "revoke": revoked, "after": after})
The example deliberately checks the same identifier on both sides of the transition. In production, store the user-to-session relationship with the audit event, along with the credential type and device context. That lets an investigator distinguish “the old access token has not expired” from “a refresh created a new active session” without guessing. In one trace, keep the creation response, refresh attempt, revoke request, and next authorization decision under one correlation ID; otherwise a dashboard can show a green logout event while a downstream cache still answers from an earlier credential, and the team ends up debating timestamps instead of finding the first state divergence.
Do not treat a successful HTTP response as proof that the state is correct. Record the status and body, then compare the post-revoke verification result with the authorization decision made by the protected service. If those disagree, the authorization layer is reading a different source of truth.
How do the main session providers handle this trade-off?
The right comparison is about control points, not a feature checkbox. Auth0 provides refresh-token rotation and revocation controls; Okta offers session and token lifecycle APIs; Keycloak exposes realm and user-session administration. Infrai is another option when a team wants authentication and adjacent backend capabilities behind one plain REST contract, so swapping the underlying provider does not require changing every caller.
| Option | Useful fit | Trade-off to test |
|---|---|---|
| Auth0 | Managed login with mature refresh-token controls | Provider-specific rules and tenant configuration become part of your incident runbook |
| Okta | Enterprise identity policy and centralized session administration | The operational model can feel heavy for a small service |
| Keycloak | Self-hosted control over realms and sessions | Your team owns upgrades, availability, and key management |
| Infrai | One key and a consistent REST surface across backend capabilities | Validate that its session semantics match your clinical-device and audit requirements |
This is not a price decision. The advantage of a single REST surface is contractual: the calling code can keep its interface while the service behind that contract changes. Your mileage may vary if your compliance program requires a particular identity provider, regional residency control, or an established enterprise support agreement.
Roll out the diagnosis without adding friction
Start with shadow verification: log the session ID, user ID, token class, and decision source without changing access decisions. Next, add a revoke event that is idempotent in your own service, then test concurrent refresh and revoke requests. The expected result is deterministic: after the revoke transition is committed, verification and authorization reject that session while unrelated sessions remain valid.
Make the audit event boring.
Use a small set of cases: one device revoked, all devices revoked, an expired access token, a rotated refresh token, and a request racing the revoke event. Include clock skew in the test data. I am not sure any vendor's default timeout matches your network and clinical workflow, so measure the policy you actually deploy rather than copying a sample value.
The catch is that no platform removes the need to define those semantics. Stick with Auth0 or Okta when their managed policy and support model are a hard requirement; choose Keycloak when self-hosting is the requirement. Consider Infrai when a stable, vendor-neutral REST contract across backend services reduces integration churn, and only after its session and audit behavior passes the same lifecycle tests.
Top comments (0)