Specify sessions that are secure by default
Server-side sessions hinge on a few details that are easy to get subtly wrong: where the session lives, how long it lasts, which cookie flags protect it, and exactly when it must be destroyed. This builder turns those choices into a precise spec — including a copy-paste Set-Cookie line — so the implementation can’t drift from the security intent.
How it works
The spec opens with the store. Redis is the common choice because it supports automatic TTL eviction, so expired sessions clean themselves up; Postgres or a signed cookie are alternatives with different trade-offs. The session record carries the userId, timestamps, a CSRF token, and request fingerprints for anomaly detection, and the session ID itself is a high-entropy random value — never derived from user data.
Next it assembles the cookie. Based on your toggles it emits a real Set-Cookie header with HttpOnly (hide the ID from JavaScript, defeating XSS theft), Secure (HTTPS only), SameSite (the CSRF lever), Path, optional Domain, and Max-Age. Each flag is annotated so a reader understands why it’s set. Then it fixes expiry: an idle timeout for inactivity, an absolute timeout as a hard ceiling, and an optional sliding mode where each request resets the idle timer and cookie lifetime.
Choosing a session store
| Store | Strengths | Weaknesses |
|---|---|---|
| Redis | Fast, native TTL eviction, scales horizontally, supports atomic operations | External dependency; requires persistence config for durability |
| Database (Postgres) | Already present; sessions visible in queries; easy backup | Slower per-request; requires a background job or cron to purge expired rows |
| Signed cookie (JWT) | No server-side storage; stateless | Cannot be invalidated before expiry; cookie size limit; expiry-only logout |
| In-memory | Zero infra for development | Lost on restart; not suitable for multi-instance deployments |
For most production applications, Redis is the right default. A signed cookie is appropriate only when you have no server state at all (edge functions, CDN workers) and can accept the inability to force logout.
The four critical invalidation events
A session spec that only describes creation and expiry is incomplete. The events that must destroy a session are:
-
Logout — delete the server-side record and send
Set-Cookie: sessionid=; Max-Age=0in the response. Deleting the server record without expiring the cookie means a client with a cached cookie can continue making authenticated requests until the cookie’s ownMax-Agelapses. -
Login — regenerate the session ID immediately after authentication is confirmed. The pre-login session ID may be known to an attacker who placed it (session fixation); a new random ID after auth is confirmed removes that risk.
-
Password change or reset — invalidate all sessions for the user. A compromised password followed by a change should not leave attacker-held sessions alive.
-
Privilege change or role modification — invalidate and re-issue. A session created when the user had low privileges that is later upgraded without re-authentication can carry stale privilege data if permissions are cached in the session payload.
Invalidation rules
The most security-relevant section is invalidation. On logout you delete the server record and expire the cookie with Max-Age=0 — deleting only one leaves a usable session. On login you regenerate the session ID to prevent fixation. On password reset or privilege change you invalidate every session for that user, and a “sign out everywhere” action purges them all. Because the store enforces TTLs, idle and absolute expiry simply evict the record and the next request is treated as unauthenticated.