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:
- 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
- The sandbox runs with
OPENHANDS_API_KEY environment variable set
- SDK calls like
workspace.get_llm() make API calls to /api/v1/users/me using Bearer authentication
- 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:
- User logs out →
token_manager.logout() is called
- Keycloak revokes the refresh token via its logout API
- BUT the
offline_tokens table is NOT cleared - the revoked token stays in the database
- 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.
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
Root Cause Analysis
The Flow When an Automation Runs:
/api/service/users/{user_id}/orgs/{org_id}/api-keys- this works correctly because it uses service-to-service authenticationOPENHANDS_API_KEYenvironment variable setworkspace.get_llm()make API calls to/api/v1/users/meusing Bearer authenticationsaas_user_auth_from_bearer()in OpenHands/enterprise/server/auth/saas_user_auth.pyThe Bug Location (OpenHands
saas_user_auth.py, lines 407-431):What Happens at Logout:
token_manager.logout()is calledoffline_tokenstable is NOT cleared - the revoked token stays in the databaseRoot 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 withSessionExpiredError.Files Involved
/enterprise/server/auth/saas_user_auth.py- Main auth logic/enterprise/server/auth/token_manager.py- Token refresh logic/enterprise/storage/api_key_store.py- API key validation/enterprise/storage/offline_token_store.py- Offline token storage/automation/utils/api_key.py- Automation's API key fetchingThis issue was created by an AI agent (OpenHands) on behalf of the user.