Skip to content

fix(auth): don't expire sessions with the Google ID token - #65

Open
rigwig wants to merge 1 commit into
jetkvm:devfrom
rigwig:fix/session-lifetime
Open

fix(auth): don't expire sessions with the Google ID token#65
rigwig wants to merge 1 commit into
jetkvm:devfrom
rigwig:fix/session-lifetime

Conversation

@rigwig

@rigwig rigwig commented Sep 5, 2026

Copy link
Copy Markdown

Related: jetkvm/kvm#1530

Summary

Cloud sessions currently die about an hour after sign-in, regardless of activity, because authenticated re-verifies the Google ID token stored in the session cookie on every request and rejects it once the token's exp passes. Google issues ID tokens for ~1h and nothing refreshes them, so the cookie's maxAge: 24h never actually applies — every client (app.jetkvm.com included) is signed out hourly. This PR replaces the token-expiry check with a real session policy.

What changed

  • authenticated no longer calls jwtVerify against Google's JWKS. The token was already verified (signature, issuer, audience) by openid-client in /oidc/callback, and the cookie is signed with COOKIE_SECRET — so the per-request verification only served to enforce a lifetime that was never meant to be a session lifetime (and cost a JWKS fetch per process).
  • Idle timeout = the cookie's existing 24h maxAge, now actually rolling: authenticated touches session.lastActiveAt (at most every 5 minutes) so cookie-session re-issues the cookie while the session is in use.
  • Absolute lifetime = 30 days from session.authenticatedAt, set in the OIDC callback. Sessions issued before this change fall back to the token's iat, so nobody is logged out by the deploy.
  • The client WebSocket upgrade path (authenticateClientRequest) now goes through the same getActiveSession() check instead of accepting any cookie that carries a token.
  • Allowlist enforcement (ALLOWED_IDENTITIES) is unchanged.

How to verify

  1. Sign in, wait > 1h (or edit the session cookie's id_token to one with a past exp), call GET /me — 200 instead of 401.
  2. Responses to authenticated requests carry a fresh Set-Cookie: session=… at most once per 5 minutes.
  3. A session with authenticatedAt older than 30 days gets 401.

Checklist

  • npm run build and npm test pass locally (61 tests, 8 new in test/auth.test.ts covering the middleware and getActiveSession)
  • Linked to issue above
  • One problem per PR (a companion PR adds a native-app sign-in handoff and depends on nothing here)
  • Tricky parts are commented in code

Context: I maintain JetPilot, an iOS client for JetKVM that talks to this API, and the hourly sign-out is the top complaint from users. Happy to adjust the lifetimes or make them configurable if you'd prefer.

The session cookie stores the Google ID token, and `authenticated` re-verified
it on every request and rejected the session once the token's `exp` passed.
Google issues ID tokens for about an hour, and nothing refreshed them, so every
client — app.jetkvm.com included — was signed out roughly an hour after login
regardless of activity, even though the cookie's maxAge is 24h.

The token was already verified (signature, issuer, audience) by openid-client
in the OIDC callback, and the cookie is signed with COOKIE_SECRET, so the
per-request JWKS verification only served to enforce a lifetime that was never
meant to be a session lifetime. Replace it with a real session policy:

- idle timeout: the cookie's existing 24h maxAge, now actually rolling —
  `authenticated` touches `lastActiveAt` (at most every 5 minutes) so
  cookie-session re-issues the cookie while the session is in use;
- absolute lifetime: 30 days from `authenticatedAt`, set at sign-in. Sessions
  issued before this change fall back to the token's `iat`.

The client WebSocket upgrade path now applies the same check via
getActiveSession() instead of accepting any cookie that carries a token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Sep 5, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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