Skip to content

sessionStateChange is not dispatched when the WebID changes while the session stays active #36

Open

Description

@bourgeoa

Summary

In @uvdsl/solid-oidc-client-browser 0.2.3 the sessionStateChange event fires only when
isActive flips. An identity change on its own — webId A → B while both states are active
— is silent. Applications that invalidate identity-derived state on this event keep answering
for the previous identity until something unrelated happens to notice.

The event payload already carries the WebID
(dist/esm/core/index.js ~2404):

dispatchStateChangeEvent () {
  this.dispatchEvent(new CustomEvent(SessionEvents.STATE_CHANGE,
    { detail: { isActive: this.isActive, webId: this.webId } }))
}

…so only the dispatch conditions need to consider it.

Where it is (and isn't) dispatched

Path Dispatch Condition
handleRedirectFromLogin() (~2151) yes unconditional ("we logged in")
restore() finally (~2188) yes if (wasActive !== this.isActive) — activeness only
logout() (~2218) yes unconditional ("we logged out")
setTokenDetails() (~2273) no every token update goes through here
WebWorkerSession.handleWorkerMessage, TOKEN_DETAILS (web ~2607) yes if (wasActive !== this.isActive) — activeness only

Why this happens in practice

A SharedWorker and a shared soidc IndexedDB per origin mean one tab can change the stored
session for all of them:

  1. Tab A is active as Alice.
  2. Bob logs in in another tab/window of the same origin (the database is cleared and rewritten).
  3. Tab A's next restore()/worker refresh reads Bob's tokens: setTokenDetails updates
    webId to Bob, isActive stays true.
  4. Both activeness-only checks are false — no sessionStateChange reaches tab A's
    listeners, and tab A continues to treat itself as Alice.

(Any in-place identity switch delivered through setTokenDetails has the same shape; the
shared-database case is the reproducible one.)

Impact

Consumers subscribing to sessionStateChange to invalidate per-identity state miss the
transition. Concrete example: SolidOS solid-logic caches which documents are writable —
editable() answers from response metadata that is not keyed by identity — and clears it
on each transition. With this gap, a tab whose WebID switched to Bob keeps showing Alice's
editability (and can act on the wrong assumption) until a refocus or a load happens to
refresh the document.

We currently work around it on the consumer side by wrapping setTokenDetails and comparing
(isActive, webId) before/after (SolidOS/solid-logic#328) — fragile, and it belongs here.

Suggested fix

Option A (smallest — keep the existing sites, add the identity term):

  • restore(): capture wasWebId next to wasActive and dispatch when
    wasActive !== this.isActive || wasWebId !== this.webId.
  • WebWorkerSession.handleWorkerMessage TOKEN_DETAILS: same comparison.

Option B (single source of truth): dispatch from setTokenDetails, since every update
(restore, redirect, worker) passes through it:

async setTokenDetails (tokenDetails) {
  const wasActive = this.isActive
  const wasWebId = this.webId
  this.information.tokenDetails = tokenDetails
  await this._updateSessionDetailsFromToken(tokenDetails.access_token)
  if (wasActive !== this.isActive || wasWebId !== this.webId) {
    this.dispatchStateChangeEvent()
  }
}

With B the site checks become redundant (and the explicit dispatches in
handleRedirectFromLogin/logout could stay for their other state changes, or be removed to
avoid duplicate events — consumers should be idempotent either way).

Worth stating in the event docs either way: listeners should treat sessionStateChange as
"the session's identity or activity may have changed".

Environment

Activity

  1. uvdsl commented on Sep 16, 2026

    @uvdsl
    Owner

    Thanks @bourgeoa!
    I did not consider the case where a session is active and then somebody logs in with a different account on top of that from a different tab (e.g., Bob).
    I thought the new tab (Bob's tab) should automatically assume the already existing session (Alice's session).
    If one wanted to switch accounts, one would need to logout, and then login with the other account.

    What behavior are you desiring?

    • Should the new tab automatically get the already existing session?
    • Should it be possible to overwrite an existing session with a different session?

    On the technical side, I think that your suggestion to add the WebID comparison (former vs. current) is a fine addition.
    I recall removing that check at some point based on my understanding above... oh well.

  2. bourgeoa commented on Sep 17, 2026

    @bourgeoa
    Author

    What behavior are you desiring?

    • Should the new tab automatically get the already existing session?

    I think yes it is simple and easy for the user and is considered a plus from your library.
    exemple in SolidOS opening a same server link in a new tab

    • Should it be possible to overwrite an existing session with a different session?

    I personally think yes.
    What is important is that the app can be aware of the change to be able to take action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions