Skip to content

Automations fail when user is logged out due to offline token dependency #92

@jpshackelford

Description

@jpshackelford

Summary

Automations and webhooks fail when a user is logged out because the authentication flow for API key-based requests (Bearer tokens) still depends on the user having a valid offline token (refresh token) in Keycloak.

Reported Issue

From Slack: https://allhandsai.slack.com/archives/C07ELRWQR3P/p1777639753033359

If I log out, I believe it currently means any webhook integrations can't start a conversation (say I had github integration) and also my automations don't run.

Root Cause Analysis

The Flow When an Automation Runs:

  1. Dispatcher creates a system API key via /api/service/users/{user_id}/orgs/{org_id}/api-keys - this works correctly because it uses service-to-service authentication
  2. The sandbox runs with OPENHANDS_API_KEY environment variable set
  3. SDK calls like workspace.get_llm() make API calls to /api/v1/users/me using Bearer authentication
  4. The auth flow fails at saas_user_auth_from_bearer() in OpenHands /enterprise/server/auth/saas_user_auth.py

The Bug Location (OpenHands saas_user_auth.py, lines 407-431):

async def saas_user_auth_from_bearer(request: Request) -> SaasUserAuth | None:
    api_key = get_api_key_from_header(request)
    # ... validation ...
    validation_result = await api_key_store.validate_api_key(api_key)  # ✅ Works
    
    # ⚠️ BUG: Still loads offline token from DB
    offline_token = await token_manager.load_offline_token(validation_result.user_id)
    
    saas_user_auth = SaasUserAuth(
        # ... with offline_token as refresh_token ...
    )
    
    # ❌ FAILS: Tries to refresh token with Keycloak
    await saas_user_auth.refresh()  # Keycloak rejects revoked token

What Happens at Logout:

  1. User logs outtoken_manager.logout() is called
  2. Keycloak revokes the refresh token via its logout API
  3. BUT the offline_tokens table is NOT cleared - the revoked token stays in the database
  4. When automation runs, it loads this invalid token and Keycloak rejects it

Root Cause

The system API keys created for automations are designed to be independent of user login state (they never expire), but the authentication flow still requires a valid Keycloak offline token to complete the request. This creates a coupling that breaks automations when users log out.

Potential Fixes

Option 1: Skip offline token refresh for system API keys

In saas_user_auth_from_bearer(), detect system API keys (by name prefix __SYSTEM__:) and skip the offline token loading/refresh step since system keys don't need session refresh.

Option 2: Generate fresh tokens for automations

When an automation needs to make API calls, generate a fresh access token using service-to-service auth instead of relying on user's offline token.

Option 3: Store offline token validity state

Track whether the offline token has been invalidated (e.g., by logout) and skip refresh for known-invalid tokens, allowing API key auth to proceed without Keycloak validation.

Same Issue Affects Jira/GitHub Integrations

The Jira integration has the same pattern - it calls get_user_auth_from_keycloak_id() which loads the offline token and then attempts operations that require token refresh. When the user is logged out, these also fail with SessionExpiredError.

Files Involved

  • OpenHands: /enterprise/server/auth/saas_user_auth.py - Main auth logic
  • OpenHands: /enterprise/server/auth/token_manager.py - Token refresh logic
  • OpenHands: /enterprise/storage/api_key_store.py - API key validation
  • OpenHands: /enterprise/storage/offline_token_store.py - Offline token storage
  • Automation: /automation/utils/api_key.py - Automation's API key fetching

This issue was created by an AI agent (OpenHands) on behalf of the user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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