Skip to content

Auth session is scoped to a single browser tab #329

@marccampbell

Description

@marccampbell

Bug

After signing in to the web frontend, auth only works in the tab that completed login. Opening the same frontend in a new browser tab behaves as logged out and requires signing in again.

Reproduction

  1. Open the ElasticClaw web frontend.
  2. Sign in with either GitHub OAuth or the access-token/password flow.
  3. Open a new tab to the same frontend URL.
  4. Observe that the new tab has no authenticated state and redirects/prompts for login.

Expected behavior

A valid web session should be usable across tabs for the same browser/profile until the session expires or the user signs out.

Actual behavior

The session token is stored in sessionStorage, which is scoped to the current top-level browsing context. A newly opened tab starts with empty sessionStorage, so the frontend sends no bearer token and the app treats the user as logged out.

Diagnosis

The login page writes both successful auth flows into tab-local storage:

  • GitHub OAuth callback stores ec_github_token with sessionStorage.setItem(...) in web/app/login/page.tsx around lines 78-81.
  • Token/password login stores ec_hub_token with sessionStorage.setItem(...) in web/app/login/page.tsx around lines 109-111.

The API client then resolves auth only from that same per-tab storage:

  • resolveToken() checks sessionStorage.getItem("ec_github_token") and sessionStorage.getItem("ec_hub_token") in web/lib/api.ts around lines 19-29.
  • getTokenSync() also reads only those sessionStorage keys in web/lib/api.ts around lines 52-57.

The main page repeats the same pattern when checking /api/auth/me:

  • web/app/page.tsx reads auth status from sessionStorage around lines 29-39.

The backend signed GitHub session token is valid for 7 days (githubSessionExpiry in pkg/hub/auth_github.go around line 22), so the short-lived behavior is not from the backend token lifetime. It is from frontend storage scope.

Likely fix

Use a browser-wide session mechanism instead of tab-local sessionStorage. Two reasonable options:

  1. Preferred: set an HttpOnly, Secure, SameSite cookie from the hub for web auth and have frontend/API calls authenticate via cookie where possible. This avoids exposing long-lived session tokens to JavaScript and naturally works across tabs.
  2. Smaller frontend-only fix: store the session token in localStorage and update all auth reads/writes/clear paths from sessionStorage to a shared helper. This restores cross-tab behavior but keeps the token JavaScript-readable.

Whichever fix is chosen, centralize token access in web/lib/api.ts or a dedicated auth storage helper; several files currently read the auth keys directly (web/app/page.tsx, web/components/sidebar.tsx, settings pages, branding hook). Add coverage or a manual test that login in one tab allows loading the app in a second tab without another sign-in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-needs-reviewThe agent is asking a human for review

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions