feat: share a sign-in across sibling subdomains - #173
Open
sea-snake wants to merge 5 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new CookieSessionStorage implementation to let sibling subdomains share sign-in awareness via a domain-scoped hint cookie, while keeping the actual delegation chain per-origin in localStorage.
Changes:
- Introduces
CookieSessionStoragethat composesLocalSessionStorageand maintains a cross-subdomain hint cookie (principal + expiry). - Exports the new storage and types from the client entrypoint.
- Adds a dedicated test suite covering cookie gating, hint derivation, and cross-source subscription notifications.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/client/cookie-session-storage.test.ts | New tests validating hint semantics, cookie gating behavior, and subscription notifications. |
| src/client/index.ts | Re-exports CookieSessionStorage and related types from the public client API. |
| src/client/cookie-session-storage.ts | New SessionStorage implementation that adds a domain-scoped hint cookie and unified change notifications. |
| src/client/auth-client.ts | Documentation updates referencing CookieSessionStorage as an option for sibling subdomains. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 24, 2026 08:53
c52e0de to
01a9f71
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 24, 2026 09:06
01a9f71 to
5c7b257
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 24, 2026 10:05
5c7b257 to
d1f8042
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 24, 2026 10:39
d1f8042 to
be8ea31
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 29, 2026 10:23
3467ddd to
2ee585f
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 29, 2026 10:36
2ee585f to
c91b392
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 29, 2026 10:53
c91b392 to
43f0068
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 29, 2026 11:27
43f0068 to
f631600
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 29, 2026 11:59
f631600 to
aed2de1
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 13:00
c1bbf80 to
35893ba
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 13:13
35893ba to
8a35fa3
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 13:24
8a35fa3 to
5e20f0e
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 13:53
5e20f0e to
f1025fa
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 14:10
f1025fa to
216e2db
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 14:21
216e2db to
6a5d59b
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 15:11
6a5d59b to
11faac6
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 15:50
11faac6 to
b3b1190
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 16:09
b3b1190 to
72fb666
Compare
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 17:07
72fb666 to
4d599f2
Compare
A cookie is the only thing that crosses between origins, so putting the state in one is what lets `chat.example.com` and `hr.example.com` share a sign-in and what lets a sign-out on either end it for both: the record becomes the domain's rather than this origin's, and removing it is what tells a sibling the sign-in is over. It carries no chain and no key. A sibling reads who is signed in and until when, and asks the identity provider to re-issue for itself rather than treating what it read as proof. Nothing raises an event when a cookie changes and no channel crosses origins, so a sibling's change is seen by looking: the Cookie Store API where the browser has it, and otherwise a re-check when the page is shown or the window regains focus, which is when the user is about to act on the answer. A domain the browser would refuse is refused here instead, naming both sides. The silent version writes nothing and reads as a sign-in that ended the moment it began. Credentials the state no longer names are dropped on the next load, which is how a sibling signing in as someone else reaches this origin.
sea-snake
force-pushed
the
feat/auth-cookie-session-storage
branch
from
August 31, 2026 17:56
4d599f2 to
7b54787
Compare
sea-snake
changed the base branch from
feat/auth-foreground-refresh
to
feat/auth-idle-bound
August 31, 2026 22:55
Its record is the only one that reaches past a single origin, so its siblings are the ones that arrive holding no credential and need the provider to have kept the session. The other two are read by the origin that wrote them, and asking a provider to persist a sign-in for them would buy nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A cookie is the only thing that crosses between origins, so putting the state in one is what lets
chat.example.comandhr.example.comshare a sign-in, and what lets a sign-out on either end it for both.BroadcastChannelcrosses origins, so a sibling's change is seen by looking: the Cookie Store API where the browser has it, and otherwise a re-check when the page is shown or the window regains focus.This is where
heldearns its keep. A cookie cannot carry a per-origin fact — every sibling reads the same bytes — so the store composes alocalStoragecompanion and reportsheldby comparing its principal against the cookie's. Comparing rather than counting matters because an expired record is kept on purpose: a sibling signing in as someone else then publishes a cookie this origin has no credential for, and asking only whether some local record exists would read that as held.Two behaviour fixes the cookie makes reachable:
onSessionGoneused to clear the state along with the credentials. Across origins that means retracting the record a sibling's ceremony just wrote, telling the sibling that did sign in that its session is gone and taking both down. It now discards this origin's claim and leaves the record standing.#hydrategets the same correction: credentials rooted at an account the state no longer names are dropped, not ended, for the same reason.getIdentity()refuses rather than handing back an anonymous identity when the record names an account this origin holds nothing for. Anonymous there is the dangerous answer: calls would go out unauthenticated while the record says someone is signed in.SessionNotHeldErroris where a silent re-issue belongs.Read hardest:
CookieStateStorage.get()composingheld, and thegetIdentity()guard.