OIDC token refresh force-reloads every other tab, dropping in-progress calls #16

Closed
opened 2026-09-12 01:50:52 -04:00 by jared · 0 comments
Owner

Severity: high · Type: bug · Confidence: high

Location: src/client/oidcTokenRefresher.ts:38-51, src/app/hooks/useSessionSync.ts:23-34, src/app/state/sessions.ts:236-249,296-317, src/app/pages/client/ClientRoot.tsx:212

Problem

LotusOidcTokenRefresher.persistTokens calls setFallbackSession, which writes
cinny_session_v1 (plus the legacy keys). Every other tab receives a storage event, and
useSessionSync treats any access-token change it did not perform as "a fresh login or a token
rotation elsewhere" and does an unconditional window.location.reload(). A routine OIDC refresh (MAS
access tokens are typically 5–15 min) therefore hard-reloads every other open tab. useSessionSync is
mounted in ClientRoot, above CallEmbedProvider, so a tab that is in an Element Call, mid-upload,
or mid-composition is destroyed on a schedule. A secondary hazard: each tab runs its own refresher
against the same stored refresh token, so with rotating refresh tokens the losing tab's refresh fails
with invalid_grant and that tab is signed out.

How to trigger

sign in via OIDC, open the app in two tabs, join a call in tab B, let tab A's access token
hit its 401-triggered refresh (or force one). Tab B reloads and the call drops.

Suggested fix

distinguish rotation from login/logout — reload only when the session disappears or the
userId/deviceId changes; on a same-device token rotation, re-read the token into the running
client (mx.http.opts.accessToken = …) instead of reloading. Ideally elect one refresher across tabs
(BroadcastChannel / Web Lock) so tabs don't race the refresh token.


Filed from the September 2026 client audit (branch lotus @ 4bea4895).

**Severity:** high · **Type:** bug · **Confidence:** high **Location:** `src/client/oidcTokenRefresher.ts:38-51`, `src/app/hooks/useSessionSync.ts:23-34`, `src/app/state/sessions.ts:236-249,296-317`, `src/app/pages/client/ClientRoot.tsx:212` ### Problem `LotusOidcTokenRefresher.persistTokens` calls `setFallbackSession`, which writes `cinny_session_v1` (plus the legacy keys). Every *other* tab receives a `storage` event, and `useSessionSync` treats any access-token change it did not perform as "a fresh login or a token rotation elsewhere" and does an unconditional `window.location.reload()`. A routine OIDC refresh (MAS access tokens are typically 5–15 min) therefore hard-reloads every other open tab. `useSessionSync` is mounted in `ClientRoot`, above `CallEmbedProvider`, so a tab that is in an Element Call, mid-upload, or mid-composition is destroyed on a schedule. A secondary hazard: each tab runs its own refresher against the same stored refresh token, so with rotating refresh tokens the losing tab's refresh fails with `invalid_grant` and that tab is signed out. ### How to trigger sign in via OIDC, open the app in two tabs, join a call in tab B, let tab A's access token hit its 401-triggered refresh (or force one). Tab B reloads and the call drops. ### Suggested fix distinguish rotation from login/logout — reload only when the session disappears or the `userId`/`deviceId` changes; on a same-device token rotation, re-read the token into the running client (`mx.http.opts.accessToken = …`) instead of reloading. Ideally elect one refresher across tabs (BroadcastChannel / Web Lock) so tabs don't race the refresh token. --- _Filed from the September 2026 client audit (branch `lotus` @ 4bea4895)._
jared added this to the Audit 2026-09 · High milestone 2026-09-12 01:50:52 -04:00
jared added the bugpriority: higharea: auth-session labels 2026-09-12 01:50:52 -04:00
jared self-assigned this 2026-09-12 01:50:52 -04:00
jared closed this issue 2026-09-12 19:46:15 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
2026-10-14
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#16