Skip to content

fix: TRAC-814 Make session token cookies expire with the session#3047

Open
chanceaclark wants to merge 1 commit into
canaryfrom
chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session
Open

fix: TRAC-814 Make session token cookies expire with the session#3047
chanceaclark wants to merge 1 commit into
canaryfrom
chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session

Conversation

@chanceaclark

@chanceaclark chanceaclark commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Jira: TRAC-814

What/Why?

authjs.session-token and authjs.anonymous-session-token must be session cookies (no Expires attribute) to comply with Essential cookie classification requirements.

Anonymous session token: removed maxAge from anonymousSignIn — without it, Next.js omits the Max-Age/Expires attribute and the cookie becomes a session cookie.

Auth session token: Auth.js v5 unconditionally sets Expires on the session cookie based on session.maxAge — there is no config option to disable this. The fix post-processes Set-Cookie headers to strip the Expires attribute from matching cookies. This is applied in two places:

  • proxies/with-auth.ts — covers session refreshes on every page request (the middleware matcher excludes /api/*)
  • app/api/auth/[...nextauth]/route.ts — covers sign-in, where Auth.js sets the initial session cookie (not reachable by middleware)

The regex matches authjs.session-token, __Secure-authjs.session-token (HTTPS prefix), and chunked variants (authjs.session-token.0, etc.). Only Expires is stripped — Max-Age=0 (used by Auth.js for cookie deletion) is left intact.

Testing

  1. Start the dev server and open DevTools → Application → Cookies
  2. Navigate to any page — verify authjs.anonymous-session-token has no Expires column value (session cookie)
  3. Sign in — verify authjs.session-token has no Expires column value
  4. Sign out and back in — verify the cookie is still a session cookie after the refresh cycle
  5. Close the browser and reopen — both cookies should be gone

Migration

No migrations required.

@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: df9d1f2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Jun 12, 2026 8:22pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 5034ea3 (2026-06-12).

No bundle size changes detected.

@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session branch from 2e5f839 to 832814b Compare June 12, 2026 18:29
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 90 93 92 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 77 93 76 86
Accessibility 95 95 95 95
Best Practices 100 100 100 100
SEO 88 88 88 88

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.5 s 3.2 s 3.6 s 4.1 s
CLS 0.001 0 0.04 0
FCP 1.2 s 1.2 s 1.2 s 1.2 s
TBT 0 ms 10 ms 0 ms 0 ms
Max Potential FID 40 ms 70 ms 50 ms 40 ms
Time to Interactive 3.5 s 3.3 s 3.7 s 4.2 s

Full Unlighthouse report →

@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session branch from 832814b to 4347627 Compare June 12, 2026 19:48
@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session branch from 4347627 to 7a7e8db Compare June 12, 2026 20:11
authjs.session-token and authjs.anonymous-session-token must be session
cookies (no Expires attribute) to comply with Essential cookie
classification requirements.

- Remove maxAge from authjs.anonymous-session-token in anonymousSignIn
- Strip Expires from authjs.session-token in the withAuth middleware,
  where Auth.js refreshes the JWT on every non-API request
- Strip Expires from authjs.session-token in the auth route handler,
  where Auth.js sets the cookie on sign-in (not covered by middleware)
- Wrap signIn and updateSession in auth/index.ts with patchSessionTokenCookies,
  which re-sets session token cookies via cookies().set() without Expires
  after Auth.js sets them — this covers the server action code path where
  Auth.js calls cookies().set(name, value, { expires: ... }) directly

Auth.js v5 unconditionally sets Expires on session cookies with no config
option to disable it. signIn/updateSession use the Next.js cookies() API
rather than response headers, so those must be intercepted separately.

Fixes TRAC-814
Co-Authored-By: Claude <noreply@anthropic.com>
@chanceaclark chanceaclark force-pushed the chancellorclark/ltrac-845-make-session-tokens-expire-with-the-session branch from 7a7e8db to df9d1f2 Compare June 12, 2026 20:21
@chanceaclark chanceaclark marked this pull request as ready for review June 12, 2026 21:34
@chanceaclark chanceaclark requested a review from a team as a code owner June 12, 2026 21:34
Comment thread core/proxies/with-auth.ts
Comment on lines +15 to +17
// Auth.js always sets Expires on session cookies based on session.maxAge. We strip it here
// so the cookie becomes a session cookie (expires when the browser closes), which is required
// for Essential cookie classification compliance.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no scenario for a consent selection that won't expire the cookie on browser close?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, consent selection cookies are handled separately and aren't affected here. The stripSessionCookieExpiry function only modifies cookies that match SESSION_TOKEN_COOKIE_RE, which targets authjs.session-token specifically. Consent cookies have their own names and expiry, so they'll persist across browser sessions as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants