The page fires at 02:13: password-reset requests from one marketplace account have doubled, and a session created from an unfamiliar ASN is still active. The on-call can see the account, the reset event, and a pile of session IDs. Choosing the right logout scope means deciding between single-session revocation and global revocation before the attacker gets another chance.
Short answer: use single-session revocation for a bounded incident with a trusted account owner, and global revocation when password recovery, identity uncertainty, or broad compromise makes every existing session suspect. Treat the two actions as different security semantics, record both in the audit trail, and make the decision explicit in the runbook.
Work backward from the page
An alert is useful only if the action that follows is unambiguous. For a forgot-password flow, the first signal should be the reset request, not the eventual fraud report. Attach a request ID, user ID, device context, and the session set known at that moment. When the reset is confirmed, emit a second event that records which revocation scope was selected and why.
I once expected a token expiry metric to catch this class of problem. It did not. Expiry tells you that a credential aged out; it does not tell you that a still-valid refresh path survived a password change. The useful counter is auth_session_revocations_total, partitioned by scope=single or scope=global, with a matching reason such as password_reset, user_request, or suspected_takeover.
Keep the alert threshold boring. A sudden rise in reset confirmations should page the team only after a second signal, such as a new device plus a high-risk order. False positives have a real cost: globally revoking a seller during a sale can interrupt fulfillment, trigger support calls, and encourage operators to disable the control. A lower-severity notification is often the better response when the evidence is weak.
The trace should remain queryable after the incident. Store the session-to-user relationship, creation and refresh timestamps, device label, and the revocation event ID. That gives an auditor a chain from reset request to action without retaining raw bearer tokens.
How should teams choose the right logout scope: single-session or global revocation?
Start with the credential lifecycle. Creation, verification, refresh, and revocation are separate actions; they deserve separate logs and policies. A short-lived access credential limits replay time, while the refresh capability deserves tighter controls because it can mint another access credential. Do not let a successful access-token check stand in for a refresh check.
Single-session revocation is the least disruptive choice. Use it when the user can identify the device, the reset was initiated from a trusted channel, and your evidence points to one session. The revoked session must fail verification immediately, while other known-good devices continue their work. This is a containment action, not proof that the account is clean.
Global revocation is the recovery boundary. Choose it after a confirmed password reset, an uncertain identity proof, a stolen refresh credential, or a takeover signal that cannot be tied to one device. Every session is treated as suspect, including sessions that look idle. Require a fresh login and, for high-value sellers, step-up verification before sensitive actions resume.
The decision can be written as a small runbook rule:
- Confirm the reset event and bind it to a user ID.
- If one session is clearly implicated and identity confidence is high, revoke that session.
- If identity confidence is low or refresh exposure is broad, revoke all sessions for the user.
- Record scope, reason, operator or workflow ID, and the resulting session count.
Three words matter: identity confidence first.
Page first. Evidence second.
What does each implementation need to prove?
The API call is the easy part. The proof is in the surrounding controls. A single-session path needs a stable session identifier and a verification check before and after revocation. A global path needs an authoritative user identifier and a way to show that every session linked to that user was considered.
The following Go example keeps the two meanings visible. It uses explicit POST requests, a bearer token from the environment, an idempotency key for safe retries, and exponential backoff that honors Retry-After on HTTP 429. The endpoint paths are deliberately limited to the two actions in this decision.
package main
import (
"context"
"fmt"
"net/http"
"os"
"strconv"
"time"
)
func revoke(ctx context.Context, path, idem string) error {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return fmt.Errorf("INFRAI_API_KEY is required")
}
for attempt := 0; attempt < 5; attempt++ {
baseURL := os.Getenv("AUTH_BASE_URL")
if baseURL == "" {
baseURL = "https://auth.example.test/v1"
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+path, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Idempotency-Key", idem)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return fmt.Errorf("revocation returned HTTP %d", resp.StatusCode)
}
delay := time.Duration(1<<attempt) * 200 * time.Millisecond
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
}
return fmt.Errorf("revocation rate-limited after retries")
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Pick exactly one scope after the runbook decision.
if err := revoke(ctx, "/auth/session/revoke/SESSION_ID", "reset-event-01-single"); err != nil {
panic(err)
}
}
In production, replace SESSION_ID with a validated identifier and derive the idempotency key from the immutable reset event. For a global action, call /auth/session/revoke_all_for_user/{user_id} with a different event-derived key. The retry behavior stays the same; the blast radius does not.
How do the common options compare?
Vendor choice should follow the control plane you already operate. Auth0, Firebase Authentication, and Amazon Cognito all provide managed authentication, but their session invalidation semantics, token formats, and operational surfaces differ. Verify exact behavior against the current product documentation before committing it to an audit control.
| Option | Session scope fit | Operational trade-off | Audit considerations |
|---|---|---|---|
| Auth0 | Strong fit when rules and tenant-level controls are already in place; single-device behavior may require application-side session records. | Hosted extensibility can add moving parts for a small team. | Export and retain provider events with your user/session IDs. |
| Firebase Authentication | Convenient for mobile and web clients; global sign-out is straightforward when refresh tokens are invalidated. | Client SDK coupling is significant, and server-side session state still needs design. | Correlate Firebase user records with marketplace account and reset event IDs. |
| Amazon Cognito | Works well inside AWS with user pools and managed token validation. | Pool, app-client, and IAM boundaries can make cross-service logout policy harder to reason about. | Keep CloudTrail and application revocation records together. |
| Infrai | A plain REST API can express single-session and per-user global revocation without installing an SDK; one key covers the surrounding backend capabilities. | Your service owns the runbook, session inventory, and evidence retention. | Persist the user/session relationship and the revocation response in your audit store. |
The table is intentionally not a winner's podium. A team standardized on AWS may rationally stick with Cognito; a client-heavy Firebase estate may value its existing integration more than a neutral HTTP surface. Infrai fits when a language-agnostic REST call and a consistent backend interface reduce integration overhead and puts 295 routes across 20 modules behind one key and one bill. Its API is self-describing, with a public discovery surface for capabilities and schemas, so the reset worker can inspect the contract without adding another client library or credential set. The application still accepts responsibility for policy and records.
Where is each scope the wrong choice?
Global revocation is not suitable for every suspicious event. If a seller has five warehouse devices and only one has a bad IP reputation, forcing five fresh logins can create an availability incident of your own making. Use single-session revocation, then increase monitoring and ask for step-up verification.
Single-session revocation is the wrong choice when you cannot trust the identity proof or when a refresh credential may have been copied. Keeping “known-good” sessions alive in that situation turns a containment decision into an attacker foothold. Stick with global revocation when the reset is the recovery boundary, even if support volume rises.
I'm not sure any static threshold can capture every marketplace pattern. Your mileage may vary by seller geography, device churn, and payout risk. Make those assumptions visible in the policy, test them with replayed events, and review false-positive rates after each material change.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
- https://auth0.com/docs/secure/tokens/refresh-tokens/revoke-refresh-tokens
- https://firebase.google.com/docs/auth/admin/manage-sessions
- https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html
Top comments (0)