Skip to content
security

Session management: do's and don'ts

Updated 2026-05-06

Session management is where authentication ends and the long tail of security risk begins. The user's correctly-authenticated session is the credential the attacker actually wants to steal, token theft via XSS, malware, repository commits, or session-replay defeats the strongest authentication.

The do's and don'ts here distill the 2026 baseline for production session handling. The big shifts since 2020: short-lived access tokens are universal, refresh token rotation is the default not the option, and JWT-in-localStorage is finally widely understood as the wrong default.

Do

  • Issue short-lived access tokens (5–15 minutes) with refresh token rotation

    Short access token lifetimes bound the blast radius of token theft. Refresh token rotation (single-use rotating refresh tokens) lets the server detect replay attempts and revoke the entire token chain.

    OAuth 2.1 requires refresh token rotation or sender-constraining for non-confidential clients. OWASP recommends access token lifetimes under 1 hour, with 5–15 minutes preferred for sensitive applications.

  • Set HttpOnly, Secure, and SameSite on session cookies

    HttpOnly prevents XSS from reading the cookie. Secure prevents accidental transmission over HTTP. SameSite=Lax (or Strict) prevents most CSRF token-leak vectors.

    OWASP Session Management Cheat Sheet and Secure Cookie best practice; default-on in modern frameworks (Next.js, Rails 7+, Django). Production cookies without these attributes have repeatedly leaked tokens via XSS and HTTP downgrade.

  • Implement server-side session revocation, not client-side only

    JWT-based sessions are convenient but cannot be revoked on the server side without a denylist. For sensitive applications, maintain server-side session state so logout-everywhere and security-event-driven revocation actually work.

    Real-world incidents (Slack 2022, GitLab 2023) drove widespread adoption of server-side session revocation as the default for production B2B SaaS. Pure JWT sessions remain acceptable for low-risk applications with short token lifetimes.

  • Use refresh token rotation with reuse detection

    When a refresh token is presented twice (the token chain has been replayed), revoke the entire chain. This catches token theft even before the user notices.

    OAuth 2.1 specifies refresh token rotation with replay detection; production breaches at multiple SaaS vendors caught attacker traffic specifically because reuse detection fired on stolen refresh tokens.

Don't

  • Don't use long-lived bearer access tokens

    A 24-hour access token gives an attacker a 24-hour window of access from any token theft. Short access tokens with refresh make the window minutes, not hours.

    OWASP and OAuth Security BCP both explicitly recommend against long-lived bearer access tokens. Multiple production breaches have traced to access tokens with 30-day lifetimes that leaked via XSS or repository commits.

  • Don't store JWTs in localStorage

    localStorage is readable by any JavaScript on the page, including XSS-injected scripts. Cookies with HttpOnly are the right answer for browser session storage.

    OWASP XSS Cheat Sheet and Auth0 / Okta security guidance: JWTs in localStorage are an XSS-amplifier. Use HttpOnly cookies instead, accepting the slight CSRF surface and mitigating with SameSite + CSRF tokens.

  • Don't share session cookies across subdomains without scope

    A cookie scoped to .example.com is sent to every subdomain, including any subdomain an attacker controls if they ever take over staging.example.com. Scope to the specific subdomain unless cross-subdomain access is required.

    Cross-subdomain cookie attacks have caused real production incidents at multiple SaaS vendors. Rule of thumb: scope cookies as narrowly as the application requires.

  • Don't accept tokens with alg=none

    JWTs with algorithm "none" are unsigned and trivially forgeable. Some libraries historically accepted them as a backwards-compatibility default, the resulting bypass class has produced multiple production CVEs.

    CVE-2015-9235 and many subsequent disclosures: JWT libraries that accepted alg=none produced authentication bypass. Modern libraries reject it by default; verify your library's behavior.

Last updated 2026-05-06.