Skip to content

fix(session): raise the default refresh_token_ttl from 1h to 1d - #145

Merged
Bccorb merged 1 commit into
mainfrom
fix/refresh-token-ttl-default
Aug 3, 2026
Merged

fix(session): raise the default refresh_token_ttl from 1h to 1d#145
Bccorb merged 1 commit into
mainfrom
fix/refresh-token-ttl-default

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closes #144

What changed

refresh_token_ttl now falls back to 1d instead of 1h:

The shipped .env.example, docker-compose.yml, and docker-compose.dev.yml now set REFRESH_TOKEN_TTL=1d too. Those explicit 1h values reproduce exactly the same one hour ceiling for anyone who copies them, which is the self hosted case the issue calls out, so leaving them at 1h would have fixed the fallback and not the default anyone actually runs. docs/configuration.md is updated to match.

Why 1d is the right number

refresh_token_ttl never gates server side acceptance. findRefreshSessionByToken (src/services/sessionService.ts:118) filters on revokedAt, expiresAt > now, and idleExpiresAt > now, and those timestamps come from computeSessionTimes (src/utils/utils.ts:33) with MAX_SESSION_LIFETIME_DAYS = 1 and IDLE_TIMEOUT_DAYS = 1.

So the server already honored a refresh token for a full day. The 1h value only told the caller otherwise, and the caller believed it. 1d makes the advertised window match the window the server actually enforces.

Contract impact

refreshTtl binds behavior, it is not advisory. The server adapter uses it as the refresh cookie's maxAge in packages/core/src/ensureCookies.ts:257 and packages/core/src/upstreamSession.ts:111, which is the mechanism behind the reported bug: the browser dropped the cookie at one hour while the session was still live.

No sibling repo changes are needed. Both SDKs read the number and pass it through, neither one branches on its value, and @seamless-auth/types types it as z.number().optional() with no bound. seamless-auth-react does not reference refreshTtl at all.

Behavior does change at runtime for any deployment relying on the fallback or on the shipped 1h, so the changeset calls that out along with how to opt back into a shorter window.

Verification

npm run typecheck   pass
npm run lint        pass
npm run format:check pass
npm run coverage    91 files, 963 passed, 1 skipped

Three tests covered the fallback and were updated: the /refresh default TTL integration test, the e2e flow, and the sessionIssuance unit test.

/security-review found no HIGH or MEDIUM findings. Rotation on use, chain revocation on replay, and hashed storage are untouched. The one real delta is that a refresh cookie now sits in the browser for 24h rather than 1h, but an attacker who could read it already held a token the server would honor for the full day, so the change does not widen server side acceptance.

Not addressed here

The React SDK still has no scheduled refresh, it refreshes on provider mount and on explicit calls only. Worth a separate issue as the issue notes.

refreshTtl is the lifetime the server adapter gives the refresh cookie, so
the 1h fallback capped a session at one hour regardless of user activity. An
app that holds state locally and makes no API calls for a couple of hours
could not refresh afterwards, so the first save returned 401.

The session row already lives one day (computeSessionTimes), and server side
acceptance is gated by expiresAt/idleExpiresAt rather than by this value, so
the old fallback under-reported the real refresh window. Raise it in both
call sites and ship 1d in .env.example and the compose files.

Closes #144
@Bccorb
Bccorb merged commit 892e616 into main Aug 3, 2026
2 checks passed
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.

fix(session): raise the default refresh_token_ttl from 1h to 1d

1 participant