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)
Symptom
DJs using
wxyc-dj-tool-iosreport 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 toGET /auth/token401s and the iOS state machine flips to.signedOut.Root cause
shared/authentication/src/auth.definition.tsdoes not configure a top-levelsessionblock, so better-auth falls back to defaults:session.expiresIn= 7 days (hard cap from last renewal)session.updateAge= 1 day (rolling renewal cadence)cookieCachesetting (defaults vary by version; safer to pin explicitly off)The 1-day
updateAgeis also when better-auth's bearer plugin emits a newset-auth-tokenresponse header — seeshared/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 onrefreshJWT, so the first 24h-renewal call effectively invalidated the client's stored bearer.The iOS half of the fix (capturing the rotated
set-auth-tokenon every auth-bearing response) lives inwxyc-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
sessionblock toauth.definition.ts: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.tsfollows the source-regex pattern fromcookie-config.test.ts: pinsexpiresIn ≥ 30 daysandupdateAge ≤ 1 dayso a future edit can't silently shorten sessions again.Acceptance
session:block present inauth.definition.tswithexpiresIn,updateAge, andcookieCache: { enabled: false }npm run typecheck,npm run lint(no new errors),npm run format:check,npm run test:unit,npm run buildall greenauth_sessionrow'sexpiresAtfor a freshly-renewed session should reflect the new TTL (now + 1 year)