Skip to content

Pin better-auth session.expiresIn so DJ sessions don't silently expire after 7 days #966

@jakebromberg

Description

@jakebromberg

Symptom

DJs using wxyc-dj-tool-ios report being signed out roughly once a day, even when they've used the app the day before. The Keychain-backed bearer token survives across launches, but the next call to GET /auth/token 401s and the iOS state machine flips to .signedOut.

Root cause

shared/authentication/src/auth.definition.ts does not configure a top-level session block, so better-auth falls back to defaults:

  • session.expiresIn = 7 days (hard cap from last renewal)
  • session.updateAge = 1 day (rolling renewal cadence)
  • no cookieCache setting (defaults vary by version; safer to pin explicitly off)

The 1-day updateAge is also when better-auth's bearer plugin emits a new set-auth-token response header — see shared/authentication/node_modules/better-auth/dist/plugins/bearer/index.mjs:57-75. The iOS app was only capturing this header on sign-in (AuthService.performSignIn), not on refreshJWT, so the first 24h-renewal call effectively invalidated the client's stored bearer.

The iOS half of the fix (capturing the rotated set-auth-token on every auth-bearing response) lives in wxyc-dj-tool-ios — that repo is the actual root cause. This PR is the backend defense-in-depth so the implicit 7-day cap doesn't silently come back to bite us if a future client is similarly inattentive, and so DJs who skip a week aren't kicked out the next time they open the app.

Change

Add an explicit session block to auth.definition.ts:

session: {
  expiresIn: 60 * 60 * 24 * 365,  // 1 year
  updateAge: 60 * 60 * 24,        // rolling renewal once per day on use
  cookieCache: { enabled: false }, // every /auth/token routes through DB so renewal + rotation happen
},

Existing 7-day sessions automatically inherit the new TTL on their next refresh; no migration needed.

Regression test in tests/unit/authentication/session-config.test.ts follows the source-regex pattern from cookie-config.test.ts: pins expiresIn ≥ 30 days and updateAge ≤ 1 day so a future edit can't silently shorten sessions again.

Acceptance

  • session: block present in auth.definition.ts with expiresIn, updateAge, and cookieCache: { enabled: false }
  • npm run typecheck, npm run lint (no new errors), npm run format:check, npm run test:unit, npm run build all green
  • After deploy, an auth_session row's expiresAt for a freshly-renewed session should reflect the new TTL (now + 1 year)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions