Skip to main content

SECTION 0 — WHAT YOU’RE REALLY BUILDING

Auth is not “login.”

Auth is a long-lived trust system across devices, networks, time, and failure.

If you’re senior fullstack, your job is to make sure:

  • users can always regain access safely

  • sessions don’t get stolen

  • refresh doesn’t become a reliability incident

  • UI states don’t lie

  • contracts remain backwards compatible


SECTION 1 — REQUIREMENTS

Functional

  • Login via email+password (or SSO).

  • Keep users signed in across browser sessions.

  • Support mobile apps (native) + web.

  • Logout from current device, optionally “logout all devices.”

  • Token/session refresh without user friction.

Non-functional

  • Security: mitigate XSS/CSRF/session fixation/token theft.

  • Reliability: refresh storms must not take down auth.

  • Correctness: predictable state transitions; no phantom “logged-in.”

  • Compatibility: versioned contracts across web/mobile releases.


SECTION 2 — CORE INVARIANTS (THE RULES)

  1. No privilege without a valid server-verified credential.

  2. Refresh tokens are never exposed to XSS on web (prefer HttpOnly cookie).

  3. Access tokens are short-lived (blast radius reduction).

  4. Rotation is enforced (replay detection).

  5. Logout revokes server-side capability, not “just delete local storage.”


SECTION 3 — RECOMMENDED MODEL: SESSION + ROTATING REFRESH

Web (browser)

  • Refresh token: HttpOnly, Secure cookie, SameSite=Lax (or Strict when possible)

  • Access token: returned in response body OR set in memory only (not localStorage)

Mobile

  • Refresh token stored in secure storage (Keychain/Keystore)

  • Access token in memory

Why

  • Web is XSS-prone → don’t expose refresh token to JS.

  • Mobile can protect secrets better.


SECTION 4 — API CONTRACT (STABLE, EXPLICIT)

Login

  • POST /auth/login

    • body: { email, password }

    • returns: { accessToken, accessTokenExpiresAt, user }

    • sets refresh cookie (web) OR returns refresh token (mobile)

Refresh

  • POST /auth/refresh

    • web: refresh token comes from cookie

    • mobile: body { refreshToken }

    • returns: { accessToken, accessTokenExpiresAt }

    • rotates refresh token (web: set-cookie; mobile: return new refresh)

Logout

  • POST /auth/logout (revoke current session)

  • POST /auth/logout-all (revoke all sessions)

Session introspection (optional)

  • GET /auth/session{ authenticated: boolean, user?, sessionId? }

Contract rules:

  • Refresh returns typed errors: INVALID_REFRESH, REVOKED, EXPIRED, REPLAY_DETECTED.

  • Never return “200 OK but you’re actually logged out.”


SECTION 5 — DATA MODEL (SERVER SOURCE OF TRUTH)

Tables (conceptual):

  • users

  • sessions

    • id

    • user_id

    • created_at, last_seen_at

    • revoked_at

    • device_fingerprint (optional)

  • refresh_tokens

    • session_id

    • token_hash

    • rotated_from_hash (optional)

    • expires_at

    • revoked_at

Senior rule:

Store hashes of refresh tokens, not plaintext.

Rotation logic:

  • when refresh happens, mark old token as used/revoked

  • issue new refresh token hash

  • if an old token is used again → replay detected → revoke the session (or all sessions depending on policy)


SECTION 6 — FRONTEND STATE MACHINE (THE TRUTH MODEL)

UNKNOWN (boot)
-> AUTHENTICATED
-> UNAUTHENTICATED

AUTHENTICATED
-> REFRESHING (token expiring)
-> AUTHENTICATED
-> UNAUTHENTICATED (refresh fails)

UNAUTHENTICATED
-> LOGGING_IN
-> AUTHENTICATED
-> ERROR

Senior UX rules:

  • Never show “logged in” until you have a validated session.

  • Handle “access token expired” as a normal path (refresh).

  • Prevent refresh loops (max attempts + backoff).


SECTION 7 — FAILURE MODES

Refresh storms

Cause:

  • clients refresh at the same time (token expiry aligned)

Mitigations:

  • add random jitter to token expiry / refresh time

  • single-flight refresh in UI (dedupe concurrent refresh requests)

  • rate limit refresh per session/user

CSRF

Risk:

  • cookie-based refresh/login can be CSRF’d

Mitigations:

  • SameSite cookies + CSRF token for unsafe methods

  • require Origin/Referer checks

  • avoid GET side effects

XSS

Mitigation:

  • keep refresh token out of JS (HttpOnly)

  • CSP + output encoding + dependency hygiene

Session fixation

Mitigation:

  • rotate session identifiers on login

  • invalidate anonymous session upon privilege elevation


SECTION 8 — OBSERVABILITY

Metrics:

  • login success/failure by reason

  • refresh success/failure by reason

  • replay-detected count

  • logout-all usage

Logs/traces:

  • include sessionId, userId, clientType (web/mobile), requestId

Alerts:

  • spike in refresh failures

  • spike in replay detected

  • spike in 401s on a critical endpoint


SECTION 9 — ROLLOUT + COMPATIBILITY

  • version refresh responses (add fields, don’t break)

  • keep old refresh behavior for one mobile release cycle

  • feature-flag rotation strictness if migrating from non-rotating refresh tokens


SECTION 10 — EXERCISES

  1. Write your auth invariants (max 8). What must never happen?

  2. Design your refresh rotation algorithm including replay detection.

  3. Create a “refresh storm” test plan (load + jitter).

  4. Define your CSRF protections for cookie-based refresh.


🏁 END — AUTH & SESSIONS CASE STUDY