Skip to content

Sign-up, log-in and sessions

2 min read · main part only; folded sections are optional

After this you can answer: Why does MiniTickets use server-side sessions instead of tokens in the browser?

sequenceDiagram
participant B as Browser
participant A as API
participant V as Valkey
B->>A: auth.login (email, password)
A->>A: argon2 verify
A->>V: SET session:hash(id), 7 days
A-->>B: Set-Cookie sid=id (HttpOnly)
B->>A: next request with sid
A->>V: GET session:hash(id)
  1. Sign-up. The password is hashed with argon2id, which is slow and memory-hungry on purpose, so stolen hashes are expensive to guess.
  2. Log-in. The API always runs one argon2 verify, even for an unknown email, so timing does not reveal which emails exist. Log-in is rate-limited per IP.
  3. Session. A new random id (32 bytes) per log-in. Valkey stores session:<sha256 of the id>, so a Valkey dump does not contain usable cookies.
  4. Every request. createContext reads the sid cookie and loads the session. A protectedProcedure answers 401 without one.

The cookie is HttpOnly (scripts cannot read it), SameSite=Lax (not sent on cross-site POSTs) and Secure in production (HTTPS only). Its life matches the Valkey entry: 7 days.

A user keeps at most 20 active sessions; the oldest is dropped. auth.logoutEverywhere deletes all of them at once. Log-out deletes the one session and clears the cookie, and calling it twice is safe.

The session holds only the user id, never the role: the Roles chapter explains why.

Code: apps/api/src/queries/sessions.ts → create and apps/api/src/trpc/context.ts → createContext.

From → toWhat happensWhat allows it
API app → Sessionsreads the session of every request
  • code createContext reads the sid cookie and asks queries/sessions.tscreateContext
Sessions → ElastiCache Valkeyonly when dataEnabled = truesession:<hash> keys with a 7-day TTL
  • code only queries/sessions.ts touches the session keyscreate
See sessions on the map →

Sessions are keys inside Valkey, so the console does not show them. ElastiCache → the cache → Events shows restarts, which log everyone out.

SymptomCauseWhere to look
Log-in works, next page says logged outSecure cookie on a plain http:// addressBrowser → Application → Cookies
Everyone is logged out at the same momentValkey restarted; sessions live only thereElastiCache → Events
Log-in returns 503 for everyoneValkey unreachable; the limiter fails closedAPI log; redis-sg
Why not a JWT?

A JWT is valid until it expires; you cannot take it back without creating a deny list, which is a server-side store again. A session in Valkey is gone the moment we delete it.

How does the cookie protect against XSS and CSRF?

HttpOnly means injected script cannot read the cookie. SameSite=Lax stops the browser sending it on cross-site POSTs. The API also checks the Origin header on every change (Security headers).

Why a new session id on every log-in?

If an attacker planted a known id in the browser before log-in (session fixation), a new id makes their copy worthless. The old cookie’s session is deleted in the same Valkey script.

Advanced: The Valkey keys

session:<hash> holds { userId, createdAt } with a 7-day TTL. user-sessions:<userId> is a sorted set of the user’s session hashes, used to cap them at 20 and to revoke all. A session is only valid while it is in that index, so if Valkey evicts the index, sessions fail closed. Both writes run in one Lua script, so they happen together.

Answer these without scrolling up. If one feels hard, reread that section.

  1. Why argon2id, and why verify even for an unknown email?
  2. What does Valkey store as the key, and why not the raw id?
  3. What do the three cookie flags each protect against?
  4. What does log out everywhere do that a JWT cannot?
  5. What happens to sessions when Valkey restarts?

Redraw it from memory: the log-in sequence: browser, API, argon2 verify, Valkey SET, cookie, next request GET

Try this: log in on the local stack, open developer tools → Application → Cookies, and read the sid cookie's flags

Read this book online: https://book.theversehub.in/