How the OAuth Flow Works for Third-Party Integrations in Omi
The Omi platform implements a standardized OAuth 2.0 authorization code flow across all third-party integrations, using CSRF state tokens stored in Redis (with JSON fallback) and automatic refresh mechanisms to maintain persistent API access.
The basedhardware/omi repository powers an AI wearable ecosystem that connects with external services like Whoop, Twitter/X, Slack, and Notion. Understanding how the OAuth flow works for third-party integrations reveals a consistent architectural pattern that balances security with developer ergonomics, enabling seamless user authentication across diverse provider APIs.
The Six-Step OAuth 2.0 Authorization Code Flow
Every integration in the Omi codebase follows an identical high-level sequence, implemented in provider-specific FastAPI routers. The flow protects against CSRF attacks while ensuring long-lived access through token refresh capabilities.
- Generate and store state token – A unique CSRF token is created and mapped to the user ID in Redis or a local JSON file.
- Redirect to provider – The user is sent to the provider’s
/authorizeendpoint withclient_id,redirect_uri,scope, and thestateparameter. - Callback verification – Upon return, the
stateparameter is validated against stored values before proceeding. - Exchange code for tokens – The authorization
codeis traded for access and refresh tokens via POST request to the provider’s token endpoint. - Persist credentials – Token data, expiry timestamps, and refresh tokens are stored for subsequent API calls.
- Automatic refresh – Before each API request, the system checks token validity and refreshes expired credentials silently.
This pattern appears in plugins/omi-whoop-app/main.py, plugins/omi-twitter-chat-tools-app/main.py, and every other integration plugin, differing only in provider-specific URLs and scopes.
Implementation Deep Dive: Whoop Integration Example
The Whoop integration demonstrates the complete OAuth implementation. Located in plugins/omi-whoop-app/main.py, this module handles the full lifecycle from initial redirect to token refresh.
Initiating the Authentication Request
When a user initiates a connection, the system generates an 8-character state token and redirects the browser to Whoop’s authorization endpoint:
# plugins/omi-whoop-app/main.py
@app.get("/auth/whoop")
async def whoop_login(uid: str = Query(...)):
# Generate short random state string (Whoop requires exactly 8 chars)
state = secrets.token_urlsafe(8)[:8]
# Store mapping between state and user ID for CSRF protection
store_oauth_state(state, uid)
# Compose provider authorization URL
params = {
"client_id": WHOOP_CLIENT_ID,
"redirect_uri": WHOOP_REDIRECT_URI,
"response_type": "code",
"scope": " ".join(WHOOP_SCOPES),
"state": state,
}
auth_url = f"{WHOOP_AUTH_URL}?{urlencode(params)}"
return RedirectResponse(url=auth_url)
The store_oauth_state function in plugins/omi-whoop-app/db.py handles persistence, writing to Redis with a 10-minute TTL or falling back to a JSON file if Redis is unavailable.
Handling the Callback and Token Exchange
After user consent, Whoop redirects to /auth/whoop/callback with the authorization code and original state. The endpoint validates the state parameter to prevent CSRF attacks before exchanging the code:
# plugins/omi-whoop-app/main.py
@app.get("/auth/whoop/callback")
async def whoop_callback(
code: str = Query(None),
state: str = Query(None),
error: str = Query(None)
):
# CSRF protection: verify state exists and matches user
uid = get_uid_from_oauth_state(state)
if not uid:
return HTMLResponse("Invalid state parameter", status_code=400)
# One-time use: remove state to prevent replay attacks
delete_oauth_state(state)
# Exchange authorization code for tokens
response = requests.post(
WHOOP_TOKEN_URL,
data={
"client_id": WHOOP_CLIENT_ID,
"client_secret": WHOOP_CLIENT_SECRET,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": WHOOP_REDIRECT_URI,
},
)
token_data = response.json()
expires_at = (datetime.utcnow() + timedelta(
seconds=token_data.get("expires_in", 3600)
)).isoformat() + "Z"
# Persist tokens for future API calls
store_whoop_tokens(
uid,
token_data["access_token"],
token_data.get("refresh_token", ""),
expires_at
)
return HTMLResponse("Connected to Whoop!", status_code=200)
The get_uid_from_oauth_state function looks up the state token in Redis or the JSON fallback, returning None if expired or missing, which immediately aborts the authentication attempt.
Automatic Token Refresh
Before making API calls, the integration checks token validity and refreshes automatically if within expiry window:
# plugins/omi-whoop-app/main.py
def get_valid_access_token(uid: str) -> Optional[str]:
tokens = get_whoop_tokens(uid)
# Check if token needs refresh
if is_token_expired(tokens["expires_at"]):
# Request new access token using refresh token
response = requests.post(
WHOOP_TOKEN_URL,
data={
"client_id": WHOOP_CLIENT_ID,
"client_secret": WHOOP_CLIENT_SECRET,
"grant_type": "refresh_token",
"refresh_token": tokens["refresh_token"],
},
)
new_token = response.json()
new_expires = (datetime.utcnow() + timedelta(
seconds=new_token["expires_in"]
)).isoformat() + "Z"
# Update stored credentials
update_whoop_tokens(
uid,
new_token["access_token"],
new_token.get("refresh_token", tokens["refresh_token"]),
new_expires
)
return new_token["access_token"]
return tokens["access_token"]
This pattern ensures API calls never fail due to expired credentials, refreshing tokens transparently before requests to Whoop endpoints.
Security Mechanisms and State Management
The Omi OAuth implementation prioritizes security through several defensive measures implemented consistently across all providers.
CSRF Protection via State Tokens
Each authentication attempt generates a cryptographically random state token using secrets.token_urlsafe(). This value acts as a nonce that binds the authorization request to the callback, preventing cross-site request forgery attacks. The state-to-user mapping persists for 10 minutes in Redis (ex=600) or indefinitely in the JSON fallback, though entries are immediately deleted upon validation.
Dual-Layer Storage Architecture
Token persistence uses a Redis-first approach with automatic JSON file fallback:
# plugins/omi-whoop-app/db.py
def store_oauth_state(state: str, uid: str):
r = _get_redis()
if r:
# Primary: Redis with 10-minute expiration
r.set(f"whoop:oauth_state:{state}", uid, ex=600)
else:
# Fallback: Local JSON file for development/edge cases
states = _load_json(OAUTH_STATES_FILE)
states[state] = {
"uid": uid,
"created_at": datetime.utcnow().isoformat()
}
_save_json(OAUTH_STATES_FILE, states)
This architecture ensures OAuth flows function in production environments with Redis while maintaining portability for local development.
Provider-Specific Variations
While the core flow remains consistent, certain providers require protocol extensions. The Twitter/X integration in plugins/omi-twitter-chat-tools-app/main.py implements PKCE (Proof Key for Code Exchange) for enhanced security:
# plugins/omi-twitter-chat-tools-app/main.py
def generate_code_verifier() -> str:
return secrets.token_urlsafe(64)[:128]
def generate_code_challenge(verifier: str) -> str:
digest = hashlib.sha256(verifier.encode()).digest()
return base64.urlsafe_b64encode(digest).decode().rstrip("=")
# Usage during authorization
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
# Store verifier for callback verification
store_oauth_state(state, uid, code_verifier=code_verifier)
The code verifier is stored alongside the state parameter and submitted during the token exchange to prevent authorization code interception attacks. This same pattern appears in integrations requiring elevated security postures.
Supported Integration Patterns
The identical OAuth architecture powers connections to:
- Twitter/X – OAuth 2.0 with PKCE
- Slack – Standard authorization code flow
- Google Calendar – Offline access with refresh tokens
- Notion – Scoped workspace authentication
- Shopify – Per-shop OAuth grants
- ShipBob, Dropbox, Linear – Standard OAuth 2.0 implementations
Each plugin contains a main.py defining the FastAPI routes and a db.py handling token storage, maintaining architectural consistency while accommodating provider-specific endpoints and scopes.
Key Files and Module Architecture
| File | Purpose | Critical Functions |
|---|---|---|
plugins/omi-whoop-app/main.py |
Whoop OAuth endpoints and token refresh | whoop_login, whoop_callback, get_valid_access_token |
plugins/omi-whoop-app/db.py |
Token and state persistence | store_oauth_state, get_uid_from_oauth_state, store_whoop_tokens |
plugins/omi-twitter-chat-tools-app/main.py |
Twitter OAuth with PKCE extension | generate_code_verifier, generate_code_challenge |
plugins/omi-slack-app/main.py |
Slack-specific OAuth implementation | Similar pattern: slack_login, slack_callback |
plugins/omi-google-calendar-app/main.py |
Google OAuth with offline access | Token refresh for background sync capabilities |
These files demonstrate that adding a new third-party integration requires only implementing the two FastAPI endpoints (/auth/<provider> and /auth/<provider>/callback) and the storage helpers in a corresponding db.py.
Summary
- OAuth 2.0 authorization code flow provides the foundation for all third-party integrations in the Omi ecosystem, implemented consistently across every plugin.
- CSRF protection is enforced via cryptographically random state tokens stored in Redis with 10-minute TTL or JSON file fallback.
- Token persistence includes access tokens, refresh tokens, and calculated expiry timestamps, enabling automatic background refresh before API calls.
- Provider variations like Twitter's PKCE implementation extend the base pattern without altering the core architecture.
- Dual-layer storage (Redis primary, JSON fallback) ensures the OAuth flow operates reliably across development and production environments.
Frequently Asked Questions
How does Omi prevent CSRF attacks during the OAuth flow?
Omi generates a unique state token using secrets.token_urlsafe() before redirecting users to the provider. This token is mapped to the user ID and stored in Redis with a 10-minute expiration. When the provider redirects back to the callback endpoint, the system validates that the returned state matches the stored value using get_uid_from_oauth_state() in db.py. If the state is missing, expired, or mismatched, the request is immediately rejected with a 400 status code, preventing cross-site request forgery attacks.
What happens if the access token expires during API usage?
The get_valid_access_token() function in each plugin's main.py automatically checks token expiry before every API call. If the token is near expiration or expired, the system uses the stored refresh token to request new credentials from the provider's token endpoint. The new access token, refresh token (if rotated), and updated expiry timestamp are immediately persisted to storage, ensuring seamless API access without user intervention.
Why does the Whoop integration use an 8-character state token while others use longer tokens?
The Whoop API specifically requires the state parameter to be exactly 8 characters, implemented via secrets.token_urlsafe(8)[:8] in plugins/omi-whoop-app/main.py. Other providers like Twitter or Google accept longer state values (typically 32+ characters). The codebase adapts to provider-specific constraints while maintaining the same CSRF protection semantics across all integrations.
Can the OAuth implementation work without Redis?
Yes, the system includes a JSON file fallback for environments without Redis connectivity. The store_oauth_state() and related functions in each db.py module check for Redis availability first; if unavailable, they read from and write to a local JSON file (oauth_states.json or similar). This dual-layer approach ensures developers can test OAuth flows locally without infrastructure dependencies while production deployments benefit from Redis's performance and TTL capabilities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →