Building a Redirect-Agnostic OAuth Flow for Mobile Clients: The Copy-Paste OAuth Pattern
What Happened
When a user attempts to authorize Gmail access from a mobile phone via Google's OAuth 2.0 flow, the standard implementation hits a fundamental problem: Google's authorization endpoint redirects to http://localhost on success, which resolves to the *user's device* — not the server where the token needs to be stored. On a Mac running authorization services, this works perfectly. On a phone, it's a dead end.
The session covered diagnosing this redirect-tunnel problem, building a phone-friendly OAuth flow, implementing security hardening, and adding network resilience through relay routing. The core system now handles OAuth codes via copy-paste on mobile, redeems them server-side with PKCE protection, and transparently falls back to SSH-relayed Google access when direct connectivity fails.
The Core Problem: Localhost in the OAuth Redirect URI
Standard OAuth flows assume the client has a reachable callback endpoint. Google's authorization endpoint redirects users to http://localhost:PORT/callback?code=AUTHCODE&state=STATE. On a Mac, this redirect lands on a local HTTP listener. On a phone, localhost means "this phone" — not the server. The user sees a "can't connect" error, but the authorization code is trapped in the phone's address bar.
The flow in /Users/cb/jada-icm/tools/phone_oauth.py solves this by splitting OAuth into two stages:
- Stage 1 (Phone): User approves on Google's consent screen, lands on the error page (which is success — the code is in the URL).
- Stage 2 (Server): User copy-pastes the full URL back; the server redeems the code for a token without needing an inbound redirect.
Technical Architecture: Copy-Paste OAuth with PKCE
The implementation uses three key primitives:
1. PKCE Code Challenge
Before generating the authorization link, the tool generates a code verifier and its SHA256 hash:
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode('utf-8').rstrip('=')
The authorization URL includes &code_challenge=...&code_challenge_method=S256. When redeeming the code, the verifier is sent back to Google, which validates it matches the challenge. This prevents code interception attacks — even if the copy-paste URL is logged or leaked, the code alone cannot be exchanged without the verifier.
2. State File for Pending Flows
The authorization URL is generated with a unique state parameter and stored in a pending-flow file at /Users/cb/jada-secrets/phone-pending-flow-{state}.json. This file holds:
- The state parameter (CSRF token)
- The code verifier (for PKCE redemption)
- The timestamp of generation
- Scope information
When the user pastes the authorization URL back, the state is extracted, the pending-flow file is loaded, and the code verifier is retrieved for the token exchange.
3. Token Redemption with Network Fallback
The redemption process attempts to exchange the authorization code for an access token by POSTing to Google's token endpoint. The basic flow is:
POST https://oauth2.googleapis.com/token
grant_type=authorization_code
code={AUTH_CODE}
client_id={CLIENT_ID}
client_secret={CLIENT_SECRET}
code_verifier={PKCE_VERIFIER}
redirect_uri=http://localhost
If the Mac's direct connection to Google times out (a transient network issue observed during the session), the tool automatically falls back to relaying through a Lightsail instance. The SSH relay is configured in ~/.ssh/config with a specific host alias, and the POST is re-attempted via that tunnel.
Infrastructure: Network Routing and Token Storage
Direct Connection
First attempt uses the Mac's default route to accounts.google.com. This is the fast path and works in most conditions.
Lightsail SSH Relay Fallback
If the direct route fails, the tool constructs a proxy command via Lightsail SSH tunnel. The tunnel is established with a pre-configured host (defined in SSH config) and used as the transport for the token-exchange POST. This ensures the request reaches Google even if the Mac's ISP or firewall blocks outbound connections to Google's IPs.
Token Persistence
On successful redemption, the access token and refresh token are stored at /Users/cb/jada-secrets/jada-token.json with metadata including the mint timestamp and token type. This persistent token is used for downstream Gmail, Calendar, and other API automation.
Key Decisions
Why Copy-Paste Over Embedded Webview?
An embedded webview in a mobile app could auto-capture the redirect, but this project doesn't have a native mobile app. Copy-paste is manual but works with any browser and requires zero app infrastructure.
Why PKCE?
PKCE (RFC 7636) is standard for public clients like mobile browsers. It prevents authorization-code interception attacks. The code verifier must be presented at redemption time — if an attacker intercepts the code from the copy-paste URL, they cannot redeem it without the verifier.
Why Lightsail Relay for Network Resilience?
During the session, the Mac's network path to Google became unreachable while AWS S3 and other services remained accessible. Rather than failing outright, the system routes token exchanges through a geographically separate relay (the Lightsail instance). This adds resilience without requiring a full backup authentication server.
Testing and Validation
The test suite at /Users/cb/icloud-repos/sites/queenofsandiego.com/tests/test_google_token.py validates:
- Token redemption correctness (code exchanged for valid token)
- PKCE verifier matching
- State parameter validation (CSRF protection)
- Token persistence and metadata
- Fallback routing when direct connection fails
What's Next
Future work includes automating the copy-paste step via deep-linking (where possible), extending the pattern to other OAuth-dependent services, and adding comprehensive incident logging to /Users/cb/icloud-jada-ops/FIRES.md for long-term reliability tracking.