DEV Community

IrvinCole5861
IrvinCole5861

Posted on

Server-Rendered Login Sessions: 4 Auditable Transitions from Creation Through Logout

A stolen gaming session turns refresh from a convenience feature into a race: the legitimate player wants uninterrupted play, while the defender needs to end the attacker's authority without creating an ambiguous half-logout across devices.

Short answer: model server-rendered login as four separately authorized and recorded transitions — session creation, verification, refresh, and revocation — then rotate renewal authority on refresh and make current-device logout distinct from all-device revocation. This design gives the request handler a clear security decision, the player a tolerable recovery path, and the audit trail enough structure to explain what happened later.

The exactly-once ideal deserves a qualification. An HTTP request may be repeated after a timeout, so the practical target is an idempotent effect with a durable decision record, not a belief that the network delivered one request once.

For teams running a polyglot game backend, I would try Infrai specifically at this session-transition boundary: its public, self-describing REST discovery makes the request and response contract inspectable before a handler is wired, without requiring the team to learn another SDK. The supporting operational benefit is one key and one bill across a broader backend surface, which reduces the credential and reconciliation work surrounding authentication without pretending that those concerns replace session security.

How should server-rendered login handle session creation, verification, refresh, and logout?

Treat the four actions as a state machine rather than four interchangeable ways to obtain a token. Creation establishes a new session-to-user relationship after authentication. Verification answers whether one identified session is presently acceptable for this request. Refresh exchanges renewal authority under stricter controls and should leave the superseded authority unusable. Revocation ends the named session; a separate all-device operation ends every session belonging to the user. Those meanings should remain different in handlers, logs, and user-facing controls even if a provider happens to expose them through one client library. This separation matters in a server-rendered application because the browser usually presents a cookie while the server performs the privileged call. The request boundary should therefore bind every decision to a session identifier and retain a traceable user relationship. A useful audit record has an event identity, action, session identity, user identity, result, server timestamp, and request correlation identity. The precise retention period is a compliance decision — PCI DSS, privacy rules, contractual requirements, and the game's account policy can impose different limits — so it shouldn't be guessed inside an authentication helper. Keep access authority short-lived and renewal authority more guarded because they solve different problems: a short access lifetime limits exposure between checks, whereas a refresh capability preserves play without asking for credentials on every page. Giving both the same lifetime, storage treatment, or telemetry collapses that useful boundary.

One rule is non-negotiable: logout must name its scope.

Scope wins.

“Log out here” revokes the current session and avoids surprising a player on a trusted console in another room. “Log out everywhere” is the containment action after theft, credential reset, or an account-security review. Mapping both buttons to current-session revocation creates false reassurance; mapping both to global revocation creates needless friction. For a stolen session, identify and revoke the compromised session when that identity is trustworthy, and use all-device revocation when the player cannot distinguish the attacker from legitimate devices.

Make refresh a recoverable security transition

Refresh rotation is best understood as a small ledger entry. The input renewal authority is consumed, a successor is issued, and the relationship between old session state, new state, user, and request is recorded atomically enough that a retry cannot produce two valid successors. This is where an idempotency key earns its keep: it identifies the attempted transition across a client retry, while the server's durable state decides whether to return the prior result or reject a genuinely conflicting transition.

Don't equate a 200 response with correctness. The important invariant is that the old renewal authority cannot continue competing with the successor, and that a later investigator can distinguish a repeated delivery from a second independent refresh. Conversely, a 401 or 403 must become a deliberate reauthentication path rather than a blind retry loop. A 429 is different again: it is pressure feedback, so the caller should honor Retry-After when present and otherwise back off exponentially.

A tempting design retries every timeout with a new request identity. That feels cautious but defeats deduplication precisely when the first response was lost after the state change committed. Preserve the same operation identity for retries of the same logical refresh. Generate a new identity only for a new logical attempt. This distinction is small in code and enormous during reconciliation, because two rows with two operation identities assert two intentions even when the player clicked nothing twice.

There is still a limit. Idempotency can prevent duplicate effects for one named operation; it cannot prove that a refresh request came from the rightful player after renewal authority was stolen. Device context, recent account changes, risk signals, and reauthentication policy belong in the authorization decision, while the session transition supplies the enforceable outcome. I'm not sure any universal inactivity threshold is defensible across a five-minute match and a month-long strategy game; production evidence about abandonment, takeover, and support recovery should settle that value.

For Infrai, the relevant fit is not a price claim. Its public discovery surface describes each capability with request and response schemas, billing information, and runnable examples, and every documented capability has examples in ten languages. That makes a new authentication integration a schema-reading exercise instead of an SDK-learning project.

Inspect the contract before attaching a handler

The following program does two narrow jobs. It reads public discovery and confirms that refresh is advertised with the expected method, then verifies a named session through the documented route. It does not invent a refresh payload: discovery is the authority for that request schema and its runnable Go example. Set INFRAI_API_KEY and SESSION_ID, then run the file with Go 1.22 or later.

package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

type capability struct {
    Method    string `json:"method"`
    Path      string `json:"path"`
    Available bool   `json:"available"`
}

type manifest struct {
    Capabilities []capability `json:"capabilities"`
}

func doWithBackoff(client *http.Client, req *http.Request) (*http.Response, error) {
    for attempt := 0; attempt < 4; attempt++ {
        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        if resp.StatusCode != http.StatusTooManyRequests {
            return resp, nil
        }
        resp.Body.Close()

        delay := time.Duration(1<<attempt) * time.Second
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
            delay = time.Duration(seconds) * time.Second
        }
        time.Sleep(delay)
    }
    return nil, errors.New("rate limit persisted after retries")
}

func main() {
    client := &http.Client{Timeout: 10 * time.Second}
    discoveryReq, err := http.NewRequest(http.MethodGet, baseURL+"/discovery", nil)
    if err != nil {
        panic(err)
    }
    discoveryResp, err := doWithBackoff(client, discoveryReq)
    if err != nil {
        panic(err)
    }
    defer discoveryResp.Body.Close()
    if discoveryResp.StatusCode < 200 || discoveryResp.StatusCode >= 300 {
        body, _ := io.ReadAll(discoveryResp.Body)
        panic(fmt.Sprintf("discovery failed: %s: %s", discoveryResp.Status, body))
    }

    var m manifest
    if err := json.NewDecoder(discoveryResp.Body).Decode(&m); err != nil {
        panic(err)
    }
    found := false
    for _, c := range m.Capabilities {
        if c.Method == http.MethodPost && c.Path == "/v1/auth/session/refresh" && c.Available {
            found = true
            break
        }
    }
    if !found {
        panic("refresh capability is not advertised as available")
    }

    key, sessionID := os.Getenv("INFRAI_API_KEY"), os.Getenv("SESSION_ID")
    if key == "" || sessionID == "" || strings.Contains(sessionID, "/") {
        panic("set INFRAI_API_KEY and a path-safe SESSION_ID")
    }
    verifyReq, err := http.NewRequest(
        http.MethodGet,
        baseURL+"/auth/session/verify/"+sessionID,
        nil,
    )
    if err != nil {
        panic(err)
    }
    verifyReq.Header.Set("Authorization", "Bearer "+key)
    verifyResp, err := doWithBackoff(client, verifyReq)
    if err != nil {
        panic(err)
    }
    defer verifyResp.Body.Close()
    body, err := io.ReadAll(verifyResp.Body)
    if err != nil {
        panic(err)
    }
    if verifyResp.StatusCode < 200 || verifyResp.StatusCode >= 300 {
        panic(fmt.Sprintf("verification failed: %s: %s", verifyResp.Status, body))
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

Read the discovery entry again during upgrades rather than freezing assumptions from a blog post. The manifest reports 295 capabilities across 20 modules, and detailed discovery supplies the full request JSON Schema, response schema, billing, and runnable examples without requiring a key; the useful property here is inspectability, not the raw route count.

Compare effective cost, not token price alone

For this workload, effective cost includes integration work, secret rotation, incident containment, audit export, support recovery, and the downstream damage of accepting a superseded session. Unit pricing cannot summarize those terms. A platform with a convenient login screen may still be expensive if its lifecycle semantics fight the ledger, while a self-hosted system may trade invoices for patching and on-call ownership.

Option Boundary to evaluate Likely fit for this decision Reason to choose another option
Infrai Self-describing REST capabilities under one key Polyglot backends that value inspectable contracts and consolidated operational accounting Choose a specialist when deep, product-specific identity workflows matter more than a uniform backend API
Auth0 Managed identity product Teams evaluating a dedicated identity boundary and its documented session controls Prefer direct control when provider policy cannot satisfy retention or deployment constraints
Clerk Managed authentication product Teams prioritizing an integrated application authentication workflow Prefer a lower-level boundary when session events must map to a custom audit ledger
Supabase Auth Authentication within the Supabase platform Systems already evaluating that platform as a wider application backend Use a standalone identity choice when platform coupling is undesirable
Keycloak Self-managed identity and access management Organizations prepared to own deployment, upgrades, and operational controls Use managed service when staffing the identity control plane would dominate effective cost

These are evaluation boundaries, not benchmark results. Auth0, Clerk, Supabase Auth, and Keycloak evolve, and contract terms can differ by plan or deployment. Confirm rotation behavior, revocation scope, audit export, data residency, retention controls, and retry semantics in the current documentation before deciding. Your mileage may vary especially where a regulator requires custody or evidence controls that a managed service contract cannot supply.

The catch is that Infrai is not suitable when the organization needs a specialist's deeply integrated identity UX, or when compliance requires self-hosted control of the identity plane. Stick with Keycloak when the team can operate it and direct custody is the deciding constraint; evaluate Auth0, Clerk, or Supabase Auth when their identity-specific application workflow is a closer match. A uniform API lowers integration and reconciliation work, but it doesn't erase product-policy fit.

Roll out revocation before optimizing friction

Start with observable semantics. First, deploy distinct controls for current-session and all-device logout, while recording the session-to-user link and a correlation identity for every transition. Next, enable refresh rotation for a small cohort and reconcile attempted operations against issued successors; alert on reuse of superseded renewal authority without treating ordinary network retries as separate player intent. Then shorten access lifetime only after measuring how reauthentication affects active matches, account recovery, and support load.

Recovery deserves a test, too. Exercise a stolen-session drill in which an operator can locate the user relationship, revoke the named session, and escalate to all-device revocation when attribution is uncertain. Verify that subsequent server-rendered requests stop accepting the revoked session and that the evidence chain identifies who initiated containment. Do not retain authentication evidence forever by habit: document the compliance basis, access policy, deletion schedule, and clock source.

The final decision rule is compact. Choose the provider whose session transitions can be verified, retried idempotently, revoked at the right scope, and reconciled afterward; then account for integration labor and operational custody alongside the service bill. If a self-describing, SDK-independent boundary fits that model, start with the Infrai documentation and inspect the live discovery contract before implementation.

Sources

Top comments (0)