Fixing Permanent OAuth Token Expiration: Moving Google Cloud OAuth from Testing to Production Mode
The Problem: 7-Day Token Refresh Cycle in Testing Mode
We've been stuck in a recurring authentication cycle where OAuth refresh tokens expire every 7 days, forcing manual re-authentication. This isn't a code bug or a token rotation issue — it's a Google Cloud platform limitation. When an OAuth application is in Testing mode, Google intentionally caps refresh token lifetime at 7 days as a security measure for development. Every 7 days, our Lambda functions, Apps Script deployments, and CLI tools lose the ability to refresh their access tokens and fail silently or with authentication errors.
The previous "fix" was a band-aid: regenerate tokens and push them to Lambda. But tokens would expire again in a week. The root cause — Testing mode configuration — was never addressed.
Why This Matters for Our Architecture
Our booking automation stack relies on several OAuth-authenticated services:
- Google Calendar API: Lambda functions in
/opt/lambda/calendar_syncquery calendar events to sync with Boatsetter, Sailo, and Viator - Google Sheets API: Apps Script in the GAS deployment pulls crew/captain data from our operational sheets
- Google Drive API: Document generation and state file updates depend on uninterrupted token access
- Gmail API: Booking confirmations, crew notifications, and digest emails are sent via service account tokens
With tokens expiring every 7 days, any of these integrations can fail. The user is forced to manually re-auth, which is operationally unsustainable at booking volume.
Technical Details: Testing Mode vs. Production Mode
Google Cloud's OAuth consent screen has two operational modes:
- Testing Mode: For development/testing. Tokens refresh every 7 days. Users are added individually. No audit logging. (This is what we're in.)
- Production Mode: For published applications. Tokens refresh every 6 months (unless revoked). Users can authenticate without being explicitly added. Full audit trail. Requires app verification and privacy policy.
Our project — stored in Google Cloud Console under the JADA Sailing project ID — currently has the OAuth app in Testing mode. Moving to Production requires publishing the app, which involves:
- Setting up a privacy policy URL (can be self-hosted or use a template)
- Configuring the OAuth brand (app name, logo, support email)
- Declaring all requested scopes and their usage
- Submitting for verification (if using sensitive scopes like email, calendar, drive)
The Fix: Moving to Production Mode
Here's the exact workflow:
Step 1: Verify Current Consent Screen Status
gcloud auth application-default login
gcloud config set project jada-sailing-prod
# Check which scopes are currently requested
gcloud projects describe jada-sailing-prod --format="value(name)"
The scopes our system requires (from existing token files and Lambda environment):
https://www.googleapis.com/auth/calendar— Calendar read/writehttps://www.googleapis.com/auth/drive— Drive file accesshttps://www.googleapis.com/auth/spreadsheets— Sheets read/writehttps://www.googleapis.com/auth/gmail.send— Gmail sending only (not inbox access)
These are all non-sensitive from Google's perspective (we're not requesting Drive full access, just file-level), so verification should be straightforward.
Step 2: Configure the OAuth Brand
Via Google Cloud Console (UI-only, no CLI equivalent):
- Navigate to APIs & Services → OAuth consent screen
- Set User Type to External (since users are Boatsetter customers + crew)
- Fill in required fields:
- App name: "JADA Sailing Booking Automation"
- User support email:
tech@queenofsandiego.com - Developer contact:
tech@queenofsandiego.com
- Add a privacy policy URL — this can be a static page served from the tech blog or a Google-hosted placeholder
- Save as draft
Step 3: Add Scopes in Consent Screen Configuration
In the same consent screen UI:
- Click Add or Remove Scopes
- Select the four scopes above
- Verify the scope descriptions are clear (Google provides defaults)
- Save
Step 4: Publish the App
Still in the consent screen UI:
- Click the radio button to change from Testing to Production
- Review the verification prompt — Google may require manual review for calendar/drive access, or may auto-approve
- Submit
Once approved (usually 24-48 hours for non-sensitive scopes), the OAuth app moves to Production mode and all new tokens generated will have 6-month refresh cycles instead of 7-day cycles.
What Happens to Existing Tokens?
Existing tokens (already generated in Testing mode) will retain their 7-day refresh lifecycle — they don't retroactively upgrade. So once Production mode is live, we need to:
- Trigger a new OAuth flow for all service accounts and Lambda environments
- Regenerate and push tokens to:
- Lambda environment variables (via CloudFormation or direct update)
- Apps Script bound scripts (via
claspdeployment with new creds) - Lightsail server token files
- Local development environment (
~/.config/gcloud/, etc.)
The re-auth is a one-time operation once Production mode is live. After that, tokens will refresh automatically for 6 months without manual intervention.
Infrastructure Changes Required
Once tokens are regenerated:
- Lambda function (
jada-calendar-sync-prodin us-west-2): Update environment variableGOOGLE_OAUTH_TOKENvia CloudFormation stack update or direct AWS Lambda console - Apps Script (deployment ID in Google Workspace): Push new token via
claspto update bound script credentials - Lightsail instance (
jada-automation-prod): Sync new token files via SSH to/var/lib/jada/tokens/ - SES configuration: If token-based auth is used, update credentials in
/etc/jada/ses.conf