Skip to content

fix(auth): bind accessToken cookie + JWT to a 7-day TTL#17

Merged
awais786 merged 1 commit into
foss-mainfrom
fix/forwardauth-cookie-ttl
May 13, 2026
Merged

fix(auth): bind accessToken cookie + JWT to a 7-day TTL#17
awais786 merged 1 commit into
foss-mainfrom
fix/forwardauth-cookie-ttl

Conversation

@awais786
Copy link
Copy Markdown

Re-opening the previously merged & reverted change (#13 / #16). Same commit ad431a19a, unchanged.

Summary

Outline's accessToken cookie was hardcoded to 3 months (addMonths(new Date(), 3)) at three call sites — outliving the rest of the foss-server-bundle's 7-day session window. After a stack-wide session expiry users would stay signed in to Outline alone.

Additionally, the /auth/redirect handler was minting JWTs with no expiresAt claim (getJwtToken(undefined, service)), making the token replayable indefinitely if it ever left the cookie. The validator at server/utils/jwt.ts:47 silently skips the expiry check when the claim is missing.

Changes

  • Export JWT_COOKIE_TTL_DAYS = 7 from server/utils/authentication.ts as a single source of truth (with rationale in a docstring).
  • Use addDays(new Date(), JWT_COOKIE_TTL_DAYS) at all three mint sites:
    • server/middlewares/authentication.ts (FORWARDAUTH login)
    • server/routes/auth/index.ts (/auth/redirect)
    • server/utils/authentication.ts (signIn callback)
  • Fix /auth/redirect to pass expires into both getJwtToken and the cookie option, so the JWT and cookie die together. Matches the pattern the other two paths already use.
  • Replace the legacy jwtToken === ctx.state.auth.token heuristic at /auth/redirect (which relied on session JWTs being deterministic — no longer true after the expiresAt fix) with an explicit type check: getJWTPayload(ctx.state.auth.token).type !== "transfer". Direct, intent-revealing.

Tests

  • server/middlewares/authentication.test.ts — asserts the FORWARDAUTH accessToken cookie's expires is ~now + JWT_COOKIE_TTL_DAYS (±60s skew).
  • server/routes/auth/index.test.ts — asserts /auth/redirect mints a JWT whose expiresAt claim is set and ~now + JWT_COOKIE_TTL_DAYS (±60s skew). JWT payload decoded with spec-correct base64url.
  • Existing should prevent token extension by rejecting JWT tokens test still passes via the new type check.

Future

If deployment-specific control becomes needed, lift JWT_COOKIE_TTL_DAYS to an env var (e.g. SESSION_TTL_SECONDS).

Outline's accessToken cookie was hardcoded to 3 months
(addMonths(new Date(), 3)) at three call sites — outliving the rest of
the foss-server-bundle's 7-day session window. After a stack-wide
session expiry users would stay signed in to Outline alone.

Changes:
- Export JWT_COOKIE_TTL_DAYS = 7 from server/utils/authentication.ts as
  a single source of truth (with rationale in a docstring).
- Use addDays(new Date(), JWT_COOKIE_TTL_DAYS) at all three mint sites:
  - server/middlewares/authentication.ts (FORWARDAUTH login)
  - server/routes/auth/index.ts (/auth/redirect)
  - server/utils/authentication.ts (signIn callback)
- Fix a pre-existing upstream issue at /auth/redirect: getJwtToken was
  called with no expiresAt arg, producing a JWT the validator at
  utils/jwt.ts:47 never rejects (claim missing → check skipped). Now
  passes `expires` into both getJwtToken and the cookie set, matching
  the pattern the other two paths already use.
- Tests cover both halves:
  - middlewares/authentication.test.ts asserts the FORWARDAUTH cookie's
    expires is ~now + JWT_COOKIE_TTL_DAYS (±60s).
  - routes/auth/index.test.ts asserts /auth/redirect mints a JWT whose
    expiresAt claim is set and ~now + JWT_COOKIE_TTL_DAYS (±60s).

In future, lift JWT_COOKIE_TTL_DAYS to an env var if deployment-specific
control is needed.
@awais786 awais786 merged commit a520a32 into foss-main May 13, 2026
23 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.

1 participant