Skip to content

fix: persist session cookie to disk to prevent PWA logout#23746

Open
kylecarbs wants to merge 1 commit intomainfrom
fix/pwa-session-cookie-persistence
Open

fix: persist session cookie to disk to prevent PWA logout#23746
kylecarbs wants to merge 1 commit intomainfrom
fix/pwa-session-cookie-persistence

Conversation

@kylecarbs
Copy link
Copy Markdown
Member

Problem

The /agents PWA logs users out frequently when the app is backgrounded or swiped away, even though the normal site doesn't.

Root Cause

The coder_session_token cookie was created without Expires or MaxAge, making it a transient session cookie that only lives in memory:

return api.DeploymentValues.HTTPCookies.Apply(&http.Cookie{
    Name:     codersdk.SessionTokenCookie,
    Value:    sessionToken,
    Path:     "/",
    HttpOnly: true,
    // no Expires or MaxAge
})
  • Normal browser: Session cookies persist because the browser process rarely fully terminates, and modern browsers restore them on restart.
  • Standalone PWA (display: standalone): Runs in its own isolated browser process. When mobile OSes kill that process (user swipes the app away), in-memory cookies are purged — forcing re-login even though the server-side API key is still valid (default 24h, refreshed on activity).

Fix

Set MaxAge on the cookie to match the API key's LifetimeSeconds, making it a persistent cookie written to disk.

This is safe because:

  • The server already validates ExpiresAt on every request
  • The server already refreshes expiry on activity (unless DisableSessionExpiryRefresh is set)
  • The security model is unchanged — MaxAge only controls whether the browser writes the cookie to disk vs. keeping it in memory
Analysis details

Cookie creation path

coderd/apikey.go:createAPIKey() → used by login, OAuth flows, and workspace app token issuance.

Session lifetime flow

  1. apikey.Generate() sets LifetimeSeconds from Sessions.DefaultDuration (default 24h)
  2. ValidateAPIKey() in httpmw/apikey.go refreshes ExpiresAt in the DB when ExpiresAt - now <= lifetime - 1h
  3. The cookie just needs to survive long enough to be sent with the next request — the server handles the rest

Why the normal site isn't affected

Regular browser tabs share a long-lived browser process. Session cookies persist across tab closes and most browsers restore them on restart. The standalone PWA gets its own WebKit/Chromium instance that the OS can terminate independently.

The session cookie (coder_session_token) was created without Expires or
MaxAge, making it a transient session cookie that only lives in memory.

In a regular browser this works fine because the browser process rarely
fully terminates. However, the /agents PWA runs with display: standalone
in its own isolated browser process. When mobile OSes kill that process
(e.g. user swipes the app away), in-memory cookies are purged — forcing
an unexpected re-login even though the server-side API key is still valid.

Setting MaxAge on the cookie to match the API key's LifetimeSeconds
makes it a persistent cookie (written to disk) that survives process
kills. This is safe because:

- The server already validates ExpiresAt on every request
- The server already refreshes expiry on activity
- The security model is unchanged; MaxAge just controls disk persistence
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