Authentication

Session Management Best Practices: Cookies, Tokens, and Rotation

Stateful sessions vs. stateless tokens, cookie security flags, refresh token rotation, idle and absolute timeouts, and how to revoke access when it matters.

Emilian GheoneaJune 10, 2026 4 min read

Logging a user in is the easy part. Keeping them logged in — securely, across requests, devices, and tabs, while still being able to kick them out instantly — is where most of the real design work lives. This article covers the decisions that make up good session management.

Stateful sessions vs. stateless tokens

There are two broad approaches to remembering that a user is authenticated.

Stateful (server-side) sessions. The server stores session data (in a database, Redis, etc.) and gives the client an opaque session ID, usually in a cookie. Every request looks the session up server-side.

  • Pros: trivial revocation (delete the row), no sensitive data on the client, easy to inspect.
  • Cons: requires a session store and a lookup on every request.

Stateless tokens (JWTs). The server issues a signed token containing the claims. No server-side lookup is needed — the signature proves validity.

  • Pros: scales horizontally with no shared session store, fast to verify.
  • Cons: revocation is hard. A valid token is valid until it expires, full stop.

In practice, the strongest designs combine both: short-lived stateless access tokens for fast per-request checks, plus a long-lived, revocable refresh token backed by server-side state. You get the performance of stateless verification and the control of server-side revocation.

Cookie security flags

If you store session identifiers or tokens in cookies — and you usually should — set these flags:

  • HttpOnly — the cookie can't be read by JavaScript, which neutralizes token theft via XSS.
  • Secure — the cookie is only sent over HTTPS.
  • SameSite — controls cross-site sending. Lax is a sensible default; Strict is tighter but breaks some cross-site navigation flows; None (which requires Secure) is needed for legitimate cross-site contexts like embedded iframes.
  • Path and Domain — scope the cookie as narrowly as the application allows.
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=900

Why HttpOnly cookies beat localStorage: tokens in localStorage are readable by any script on the page, so a single XSS bug exfiltrates them. An HttpOnly cookie is invisible to JavaScript, so the same bug can't steal it directly.

Access tokens and refresh tokens

The two-token pattern works like this:

  • The access token is short-lived (often 5–15 minutes) and presented on each request. Because it expires quickly, a stolen access token has a small window of usefulness.
  • The refresh token is long-lived, stored securely (ideally an HttpOnly cookie), and used only to obtain new access tokens. It's tracked server-side so it can be revoked.

When the access token expires, the client silently exchanges the refresh token for a new access token. The user never notices.

Refresh token rotation

A long-lived refresh token is a juicy target, so harden it with rotation: every time a refresh token is used, issue a brand-new one and invalidate the old.

The payoff is theft detection. If an attacker steals a refresh token and uses it, the legitimate user's next refresh will present the now-invalidated old token. Detecting reuse of a rotated token is a strong signal of compromise — at which point you revoke the entire token family and force re-authentication.

1. Client refreshes with token A  -> server issues token B, invalidates A
2. Attacker later replays token A  -> server sees a used/invalid token
3. Server revokes the whole family -> both parties must log in again

Timeouts: idle vs. absolute

Two different limits, and you want both:

  • Idle timeout — the session ends after a period of inactivity (e.g. 30 minutes). Protects unattended sessions.
  • Absolute timeout — the session has a hard maximum lifetime regardless of activity (e.g. 7–30 days). Bounds how long a single login can last before re-authentication.

Sensitive operations (changing a password, payment details, deleting data) should additionally require step-up authentication — re-confirming identity even within a valid session.

Revocation: the feature you'll wish you had

At some point a user will report a lost laptop, or you'll detect a compromised account, and you'll need to end sessions right now. Build this in from the start:

  • Maintain a server-side record of active sessions/refresh tokens so each can be revoked individually.
  • Offer users a "sign out everywhere" action and a list of active sessions with device and location info.
  • On password change, invalidate all existing sessions by default.
  • Keep access tokens short-lived so that even pure-stateless revocation has a small ceiling on how long a revoked user stays authenticated.

Cross-tab and cross-device consistency

Users expect signing out in one tab to sign them out everywhere, and refreshing a token in one tab shouldn't break another. The storage event and the BroadcastChannel API let tabs coordinate session state. For multi-device consistency, server-side session records are the source of truth.

A practical default

For most web applications, a solid baseline is:

  • Short-lived access token (5–15 min), long-lived rotating refresh token in an HttpOnly, Secure, SameSite cookie.
  • Server-side session/token records for revocation.
  • Idle timeout plus an absolute lifetime.
  • "Sign out everywhere," active-session listing, and full invalidation on password change.
  • Step-up auth for sensitive actions.

None of this is glamorous, but session management is where authentication quietly succeeds or fails. The systems users trust most are the ones that keep them logged in seamlessly — and can throw them out instantly the moment something's wrong.

Written by

Emilian Gheonea

Senior Blockchain & Full-Stack Software Engineer. I build EmbedAuth — an embeddable authentication platform for SaaS — and write about the auth problems most teams hit too late.