--- id: TASK-76 title: 'Persist EncryptID login across subdomains, sessions, and browsers' status: Done assignee: [] created_date: '2026-03-01 22:12' labels: - encryptid - auth - session dependencies: [] references: - shared/components/rstack-identity.ts - src/encryptid/server.ts (refresh endpoint accepts expired tokens) priority: high --- ## Description EncryptID sessions were lost when navigating between rspace.online subdomains (e.g. demo.rspace.online → cca.rspace.online) because localStorage is per-origin. Sessions also didn't survive token expiry gracefully. Added cross-subdomain cookie persistence alongside localStorage, with automatic refresh of expired tokens via the server. ## Acceptance Criteria - [ ] #1 Session persists when navigating between *.rspace.online subdomains - [ ] #2 Session survives browser close/reopen (30-day cookie) - [ ] #3 Expired tokens auto-refresh via server before being discarded - [ ] #4 Sign Out clears both localStorage and cookie - [ ] #5 Code that directly reads localStorage still works via early cookie→localStorage sync ## Final Summary Added cross-subdomain cookie (`eid_token`, `domain=.rspace.online`, 30-day max-age, `SameSite=Lax`, `Secure`) to `rstack-identity.ts`. Three layers of persistence:\n\n1. **Cookie helpers** — `_setSessionCookie()`, `_getSessionCookie()`, `_removeSessionCookie()` handle domain-wide cookie\n2. **`getSession()` fallback** — tries localStorage first, falls back to cookie, restores to localStorage for fast access\n3. **`#refreshIfNeeded()` upgrade** — attempts server refresh for expired tokens before giving up; server accepts expired tokens via `{ exp: false }`\n4. **Early IIFE sync** — at module load time, syncs cookie→localStorage so direct `localStorage.getItem()` callers (WebSocket auth, sync, shell scripts) see the session\n\nCommit: ef1d93d. Merged dev→main, pushed to Gitea.