---
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

<!-- SECTION:DESCRIPTION:BEGIN -->
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.
<!-- SECTION:DESCRIPTION:END -->

## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #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
<!-- AC:END -->

## Final Summary

<!-- SECTION:FINAL_SUMMARY:BEGIN -->
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.
<!-- SECTION:FINAL_SUMMARY:END -->
