Implementation:OpenHands OpenHands AuthTokenStore
| Knowledge Sources | |
|---|---|
| Domains | Authentication, OAuth, Storage |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Concrete tool for securely storing and refreshing OAuth tokens with atomic database operations and race-condition prevention, provided by the OpenHands enterprise storage layer.
Description
The AuthTokenStore class manages the persistence and retrieval of OAuth access and refresh tokens for third-party integrations (GitHub, GitLab, Linear, Slack, etc.). It is designed for a concurrent server environment where multiple requests may attempt to refresh the same token simultaneously.
The store_tokens method persists a token pair (access token and refresh token) along with their expiration metadata. Tokens are encrypted before storage using the encryption utilities from the encrypt_utils module. The method performs an upsert operation, creating a new token record if none exists for the given provider and user, or updating the existing record.
The load_tokens method retrieves stored tokens for a given provider and user. Critically, it uses a SELECT...FOR UPDATE database locking strategy to prevent race conditions during token refresh. When multiple concurrent requests detect an expired access token and attempt to refresh it simultaneously, only the first request acquires the row lock and performs the refresh; subsequent requests wait for the lock and then read the already-refreshed token. This eliminates duplicate refresh requests that could invalidate tokens or trigger provider rate limits.
The is_access_token_valid method checks whether the stored access token for a given provider and user has not yet expired, accounting for a configurable clock skew buffer to avoid using tokens that are about to expire.
The is_refresh_token_valid method checks whether the stored refresh token is still valid and usable for obtaining a new access token. Some providers issue refresh tokens with their own expiration; this method handles both expiring and non-expiring refresh token patterns.
Usage
Use AuthTokenStore whenever the application needs to authenticate against a third-party API on behalf of a user. Call load_tokens with the appropriate locking semantics before making API calls. If the access token is expired, use the refresh token to obtain a new pair and call store_tokens to persist the updated credentials.
Code Reference
Source Location
- Repository: OpenHands
- File: enterprise/storage/auth_token_store.py
- Lines: 1-208
Signature
class AuthTokenStore:
async def store_tokens(
self,
provider: str,
user_id: str,
access_token: str,
refresh_token: str,
access_token_expires_at: datetime,
refresh_token_expires_at: datetime | None = None,
) -> None:
...
async def load_tokens(
self,
provider: str,
user_id: str,
for_update: bool = False,
) -> dict | None:
...
def is_access_token_valid(
self,
token_record: dict,
clock_skew_seconds: int = 60,
) -> bool:
...
def is_refresh_token_valid(
self,
token_record: dict,
) -> bool:
...
Import
from enterprise.storage.auth_token_store import AuthTokenStore
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| provider | str |
Yes | The OAuth provider identifier (e.g., "github", "gitlab", "linear")
|
| user_id | str |
Yes | The internal user ID whose tokens are being managed |
| access_token | str |
Yes | The OAuth access token to store (for store_tokens) |
| refresh_token | str |
Yes | The OAuth refresh token to store (for store_tokens) |
| access_token_expires_at | datetime |
Yes | Expiration timestamp of the access token (for store_tokens) |
| refresh_token_expires_at | datetime or None |
No | Expiration timestamp of the refresh token; None for non-expiring refresh tokens (for store_tokens)
|
| for_update | bool |
No | When True, acquires a SELECT...FOR UPDATE row lock to prevent concurrent refresh races (for load_tokens); defaults to False
|
| token_record | dict |
Yes | The token record dictionary as returned by load_tokens (for is_access_token_valid and is_refresh_token_valid) |
| clock_skew_seconds | int |
No | Buffer in seconds before actual expiration to consider the token invalid; defaults to 60 (for is_access_token_valid)
|
Outputs
| Name | Type | Description |
|---|---|---|
| token_record | dict or None |
Decrypted token record containing access_token, refresh_token, and expiration metadata; None if no tokens found (from load_tokens)
|
| is_valid | bool |
Whether the access or refresh token is currently valid and usable (from is_access_token_valid / is_refresh_token_valid) |
Concurrency Model
The SELECT...FOR UPDATE locking strategy in load_tokens prevents the following race condition:
- Request A detects expired access token and begins refresh.
- Request B also detects expired access token and begins refresh concurrently.
- Without locking, both requests would call the provider's token refresh endpoint, potentially invalidating the token refreshed by Request A.
- With for_update=True, Request B blocks on the row lock until Request A completes the refresh and releases the lock.
- Request B then reads the already-refreshed token, avoiding a duplicate refresh.
Usage Examples
from enterprise.storage.auth_token_store import AuthTokenStore
from datetime import datetime, timedelta
store = AuthTokenStore(db_session=session)
# Store new OAuth tokens
await store.store_tokens(
provider="github",
user_id="user-123",
access_token="gho_abc123",
refresh_token="ghr_xyz789",
access_token_expires_at=datetime.utcnow() + timedelta(hours=8),
refresh_token_expires_at=datetime.utcnow() + timedelta(days=180),
)
# Load tokens with row-level locking for safe refresh
token_record = await store.load_tokens(
provider="github",
user_id="user-123",
for_update=True,
)
if token_record and not store.is_access_token_valid(token_record):
if store.is_refresh_token_valid(token_record):
# Perform token refresh with provider
new_tokens = await github_oauth.refresh(token_record["refresh_token"])
await store.store_tokens(
provider="github",
user_id="user-123",
access_token=new_tokens["access_token"],
refresh_token=new_tokens["refresh_token"],
access_token_expires_at=new_tokens["expires_at"],
)