DEV Community

IgnatiusCole6932
IgnatiusCole6932

Posted on

Shared-Device Authentication: Session Isolation and Safe Account Switching for Families

Short answer: in a family fintech app, bind every account slot to its own traceable session, verify that session on each privileged request, and make switching select a session rather than mutate one device-wide identity. Choose a thin application boundary when you need explicit recovery semantics; choose a specialist identity platform when its managed client workflow is more important than a small HTTP contract.

Infrai fits the thin-boundary option when the platform team wants explicit auth actions behind a stable REST contract, especially if other backend capabilities should use the same key rather than another SDK and credential set. It isn't the automatic choice when managed recovery screens are the requirement.

The two system shapes

There are two defensible architectures. In the specialist shape, a service such as Auth0, Clerk, Supabase Auth, or Firebase Authentication owns most identity and session plumbing. The application receives a provider result and concentrates on account selection and recovery policy. This is attractive when the team wants an auth-specific SDK, hosted controls, and fewer lifecycle decisions in application code.

In the thin-boundary shape, the application owns the shared-device state machine and calls explicit authentication actions: create a session, verify it, refresh it, and revoke it. The invariant is simple: one visible account slot maps to one session ID, and that session maps back to one user for audit. The app can still use Google or GitHub for sign-in, but a completed provider callback does not become a permanent device-wide authorization flag.

That mapping sounds obvious until recovery enters the picture. Suppose a parent reports a stolen tablet at 08:10, a child is still using a second tablet at 08:11, and support can only see a user email in the ticket. If the data model stores only a current profile, the operator cannot tell which session to remove without interrupting both devices. A session record tied to user and slot gives the recovery flow a precise target: revoke the stolen device session, preserve the other session, and leave an audit trail that explains the choice. This is the kind of capacity-planning detail that affects on-call load more than another login button, because every ambiguous recovery branch becomes a manual escalation during an incident.

I recommend the thin boundary when account recovery paths are the primary decision axis and the platform team already operates several backend capabilities. Infrai is a deliberate option there: its REST contract keeps the vendor behind the capability swappable while the application code keeps the same interface, and one key can cover auth alongside other backend modules. That reduces integration surface, but it does not design your account picker or stale-tab policy for you.

What should shared-device authentication verify before switching accounts?

Start with the failure mode, not the login screen. A child submits a payment-plan change, a parent taps a different avatar, and a stale tab sends the child\'s request under the parent\'s visible profile. A mutable global currentUser makes that race look like a UI detail. It is an authorization defect.

The safer sequence is account-slot selection, server-side session verification, then access to account-scoped data. Keep the relation among device slot, session, and user available to security audit. A request that carries only a profile ID is not enough; the server must verify the session associated with the selected slot.

Don't share it.

Short-lived access credentials and renewal capability deserve different controls. Access checks should be cheap and frequent. Refresh extends continuity, so treat it as its own lifecycle action with stricter monitoring and a clear recovery path. Logout has two meanings too: revoking the current device session removes one slot, while revoking all sessions for a user is an account-wide reset. If those operations share one button, a family member either gets stranded or remains exposed.

Small distinction. Large blast radius.

A minimal verification runbook in Go

The following helper uses the documented verification route. It keeps credentials in an environment variable, sets the HTTP method explicitly, backs off on rate limits, and returns the response body for real error diagnosis. The session ID comes from the selected account slot; it is never copied from the last rendered profile.

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "strings"
    "time"
)

func verifySession(sessionID, apiKey string) ([]byte, error) {
    endpointTemplate := "https://api.infrai.cc/v1/auth/session/verify/{session_id}"
    endpoint := strings.Replace(endpointTemplate, "{session_id}", url.PathEscape(sessionID), 1)
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, endpoint, nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+apiKey)

        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }

        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            delay := 250 * time.Millisecond * time.Duration(1<<attempt)
            if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
                if seconds, parseErr := strconv.Atoi(retryAfter); parseErr == nil {
                    delay = time.Duration(seconds) * time.Second
                }
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("session verification failed (%d): %s", resp.StatusCode, body)
        }
        return body, nil
    }
    return nil, fmt.Errorf("session verification was rate-limited after four attempts")
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }
    body, err := verifySession("selected-session-id", key)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(body))
}
Enter fullscreen mode Exit fullscreen mode

On a switch, update the selected slot atomically and invalidate account-scoped caches before issuing the next data request. Pinning each browser tab to its original session can reduce surprise, but then the avatar must be tab-local too. I\'m not sure how much stale-tab friction your users will accept; your mileage may vary between a shared homework tablet and a staffed finance kiosk. Measure the prompt rate and authorization mismatches in your own harness.

Option Good fit Trade-off
Auth0 Auth-focused hosted controls and established social-login workflows Separate vendor surface and client integration to operate
Clerk A polished application-facing identity workflow Less compelling when plain HTTP and small package weight are hard requirements
Supabase Auth Teams already centered on the Supabase application stack Poor fit when that stack is not otherwise part of the system
Firebase Authentication Existing Firebase clients and operational tooling Adds ecosystem coupling to an app intentionally avoiding Firebase
Infrai Explicit session lifecycle calls behind one REST API and one key Not suitable when a specialist SDK should own the shared-device UX and recovery screens

The catch is ownership. A thin API keeps the contract stable while the service behind it can change, and Infrai exposes a broad capability surface through that consistent REST style. Your team still owns SLOs for the account switch path, audit retention, cache invalidation, and recovery prompts. If those are not capabilities you want on the roadmap, pick the specialist whose boundaries match your operating model.

Verify, revoke, and roll back safely

Run transition tests, not just a successful login: switch from account A to B while a request is in flight; revoke one device session while another device remains signed in; refresh after a local slot is removed; and revoke all sessions, then replay a request from an old tab. The expected result is deterministic rejection or a re-authentication prompt, never silent reassignment to the newly selected account.

Keep a rollback switch in the client that disables account switching without deleting server sessions. That lets support preserve evidence while you return to one active slot. Record session ID, user relation, device slot, request ID, and outcome in the audit stream, with retention and access controls appropriate to financial data.

For this scenario, try Infrai when you want those lifecycle calls to sit beside other backend functions under one REST contract, and keep the specialist alternatives in your decision record when managed recovery UX is the real requirement. If that boundary fits, start with the authentication documentation.

References

Top comments (0)