Skip to content

bug: OAuth sessions deleted on re-login, breaking multi-device usage #21647

@gboston

Description

@gboston

Description

Each OIDC login deletes all existing OAuth sessions for the same user+provider before creating a new one. This means logging in from a second device/browser invalidates the first device's session, causing tool calls to fail with "Missing Authorization header" until the user re-authenticates.

Steps to Reproduce

  1. Log in on Device A (e.g., desktop browser) — OIDC session created, oauth_session_id cookie set
  2. Log in on Device B (e.g., mobile/PWA) — new session created, Device A's session deleted from DB
  3. On Device A, trigger a tool call that requires system_oauth authorization
  4. Tool call fails — middleware looks up the session ID from the cookie, but the row no longer exists in oauth_session

Expected Behavior

Multiple concurrent OIDC sessions should coexist. Logging in on Device B should not invalidate Device A's session.

Root Cause

In backend/open_webui/utils/oauth.py, the handle_callback() method in OAuthManager (around line 1682) deletes all existing sessions for the user+provider before creating a new one:

# Clean up any existing sessions for this user/provider first
sessions = OAuthSessions.get_sessions_by_user_id(user.id, db=db)
for session in sessions:
    if session.provider == provider:
        OAuthSessions.delete_session_by_id(session.id, db=db)

The same pattern exists in OAuthClientManager.handle_callback() (line 879-883) for MCP tool sessions.

Why Refresh Doesn't Help

The OIDC token refresh mechanism works correctly — we confirmed sessions expired for up to 5 days are successfully refreshed using Okta's 7-day refresh token. The problem is that the session row is deleted, so get_session_by_id_and_user_id() returns None before any refresh logic is reached.

Architecture Already Supports Multi-Session

The rest of the codebase is already compatible with multiple concurrent sessions:

  • Database schema: oauth_session table has no unique constraint on (user_id, provider) — multiple rows per user/provider are valid
  • Cookie handling: oauth_session_id cookie stores the session UUID, so each device naturally points to its own session
  • Middleware lookup: Uses get_session_by_id_and_user_id(session_id, user_id) — looks up by specific UUID, not by provider
  • Logout flow: Deletes only the current session (by cookie UUID), leaving other sessions intact
  • Token refresh: Operates on specific session by ID

Suggested Fix

  1. Remove the session deletion loop in OAuthManager.handle_callback() for OIDC sessions (the for session in sessions block)
  2. Optionally add a max session limit (e.g., 10 active OIDC sessions per user) to prevent unbounded growth — prune oldest sessions when the limit is exceeded
  3. The OAuthClientManager (MCP tool sessions) may reasonably keep the replace-on-reauth behavior since those tokens are typically one-per-provider

Environment

  • Open WebUI version: v0.8.3 (also confirmed in main branch)
  • OIDC Provider: Okta (access token: 1h, refresh token: 7d)
  • Deployment: Google Cloud Run

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions