What each holds
A cookie is a small key-value pair in the browser, sent automatically on each request as the carrier of identity. A session is server-side user state; usually only a random session id goes into the cookie while the real data stays on the server.
Login flow
- The user logs in; the server verifies the password, creates a session, and stores
user_id; - It writes the session id into a cookie and returns it;
- On later requests the browser sends the cookie, and the server looks up the user by id.
JWT is not a session
A JWT encodes state into the token itself (with a signature), so the server may skip the lookup. Good for stateless services, but hard to revoke -- the token stays valid until expiry, unlike a session you can delete at will.
Cookie security flags
- HttpOnly: blocks JS reads, defends against XSS token theft;
- Secure: HTTPS only;
- SameSite: limits cross-site sending, easing CSRF.
Try it
Generate a strong session secret: Password generator.
Choosing and scaling session storage
- In-process memory: simplest on one node, but flaky across instances and needs migrating as soon as you scale out;
- Shared store such as Redis: the common choice, set expiries, pick a serialisation format, pool connections, and plan degradation (reject new logins rather than letting everything through);
- Database: suits auditing and long retention, but a query per request becomes a bottleneck, so add caching;
- Encrypted cookie: keeps the server stateless at the cost of size limits and hard-to-revoke sessions;
- Capacity planning: estimate active users times average session size, add peak headroom, and avoid saturating the store during a sale.
Expiry and renewal
Set both idle and absolute timeouts: the first expires after inactivity, the second caps total lifetime. Sliding renewal should extend the idle timeout but never the absolute one; for sensitive actions (password change, payment) re-authenticate rather than trusting the session.
Sessions across platforms and services
- Decide multi-device policy first: whether one account may be signed in on several devices and whether a new login evicts the old session shapes storage design, settle it before implementation.
- Pass identity explicitly between services: internal calls should carry the caller's identity rather than relying on shared session storage, or internal requests bypass authorisation.
- Keep sessions lean: store identifiers and necessary state, fetching details on demand; oversized sessions cost storage and raise per-request transfer and parsing.
- Tolerate stateless components: gateways and edge functions that cannot reach shared storage should receive limited information in a token instead of requiring session access.
- Monitor anomalies: one session appearing in two locations in a short window, or a spike in sessions per account, should alert and support fast invalidation.
The key is deciding up front who stores state and who verifies it, separating those makes scaling and hardening much easier.