Building Resilient Multi-Account OAuth Token Management for JADA Operations
What Was Done
This session focused on debugging and hardening the OAuth 2.0 re-authentication system for JADA's multi-account Google integration. The work involved resolving token refresh failures, unifying legacy and new token paths, validating OAuth scopes across token files, and establishing monitoring infrastructure to catch token state drift before it impacts production workflows.
Technical Architecture
JADA's Google integration evolved through two token management approaches:
- Legacy token path: Individual token files stored in user home directories with limited scopes (e.g.,
~/.jada_gmail_token.json) - Unified token path: Centralized token storage at
/Users/cb/icloud-repos/tools/tokens/with comprehensive scopes includinggmail.compose,gmail.modify, andgmail.readonly
The re-authentication system is split into three components in /Users/cb/icloud-repos/tools/:
reauth_jada_all.py— Orchestrates OAuth 2.0 authorization flow, spawns local HTTP listener, handles refresh token exchangefinish_auth.py— Receives OAuth redirect callback, exchanges authorization code for access/refresh tokenswatch_auth_email.py— Polls Gmail inbox for OAuth consent emails and automatically extracts authorization URLs
OAuth Flow Implementation
The re-authentication flow is server-driven but requires user interaction:
1. reauth_jada_all.py spawns HTTP listener on localhost:8080
2. Script opens browser to Google OAuth consent screen
3. User grants permissions; browser redirects to localhost:8080/callback
4. finish_auth.py receives authorization code in redirect URL
5. Code is exchanged for tokens (access + refresh)
6. Tokens written to unified path with full scope manifest
The critical decision here was moving to a unified token directory with explicit scope tracking. Legacy approach scattered tokens across home directories with no audit trail of which scopes were granted. The unified path enables:
- Scope validation before API calls (fail fast if token lacks required scope)
- Token rotation policies (single source of truth for expiration)
- Audit logging (token request timestamps, scope evolution)
Token Scope Validation and Debugging
The session uncovered a critical issue: legacy gmail-full tokens were failing refresh with HTTP 400 errors. Root cause was scope drift — the token was created with https://www.googleapis.com/auth/gmail.modify, but the re-auth system expected gmail.compose (a subset of gmail.readonly + write permission).
Debugging approach:
# Inspect token scopes in unified directory
jq '.scopes' /Users/cb/icloud-repos/tools/tokens/jada_unified_token.json
# Check legacy token for comparison
jq '.scopes' ~/.jada_gmail_token.json
# Verify jada_google.py helper validates scopes before use
grep -A 5 'def requires_scope' /Users/cb/icloud-repos/tools/jada_google.py
The fix involved:
- Capturing full scope manifest in token metadata during OAuth callback
- Adding pre-flight scope validation in
reauth_jada_all.py(lines 87–102) that cross-checks requested scopes against token grants - Implementing 15-second timeout on token refresh calls to surface network errors vs. permission errors
Monitoring and State Observation
A long-running token watcher was established to detect when OAuth re-auth succeeds:
# Watch for new gmail.compose scope in token (2-minute polling, 4-hour max wait)
watch -n 120 'jq ".scopes[]" /Users/cb/icloud-repos/tools/tokens/jada_unified_token.json | grep gmail.compose'
This pattern emerged from the need to verify that re-auth actually completed. OAuth flows are asynchronous (user must click browser consent button), so the monitoring script polls the token file for the presence of newly-granted scopes. When gmail.compose appears in the token metadata, we know:
- User completed consent in browser
- Authorization code was successfully exchanged
- Token file was written to disk with updated scopes
- Downstream workflows can safely proceed
Operational Integration Points
The OAuth system integrates with operational tools:
- Gmail drafts API: Uses unified token to create/modify message drafts via
gmail-fullscopes - Email watchers:
watch_auth_email.pypolls Gmail for OAuth consent emails; relevant for flows where OAuth link is sent via email rather than displayed locally - SMS nudging: When OAuth listener is open and waiting for user action, SMS can be sent via
jada_sms.pyto prompt user to complete browser consent
Key Decisions
Why unified token directory instead of scattered home-dir tokens: Operational visibility. When running JADA automation across multiple machines and user contexts, token state must be queryable and auditable. A single `/tokens/` directory with JSON metadata enables monitoring tooling to detect stale tokens before they fail in production.
Why explicit scope metadata in token files: Scopes are a contract between the application and Google's authorization server. If token refresh fails due to scope mismatch, the error is opaque (HTTP 400) without metadata. Storing the granted scopes as JSON in the token file enables the application to validate expectations before API calls.
Why long-running OAuth listeners instead of callback servers: Simpler deployment. The re-auth script can run on any machine with a browser; no need for a publicly routable callback endpoint. The tradeoff is that localhost:8080 only works for interactive sessions, not headless automation — for headless token refresh, the system falls back to google-auth-oauthlib's refresh token flow.
What's Next
Remaining hardening work:
- Implement token expiration awareness in
jada_google.py— check token metadata before API calls, refresh preemptively if expiry < 5 minutes - Add metrics export (token age, scope mismatch errors) to enable alerting on stale tokens
- Document OAuth flow and token paths in
/Users/cb/icloud-repos/tools/CONTEXT.mdfor team reference - Test OAuth callback handling under network latency (slow DNS, redirects) to ensure timeout logic is robust