Fixing Permanent OAuth Token Expiration: Moving Google Cloud Apps from Testing to Production Mode

One of the most frustrating operational issues we've faced is OAuth token expiration forcing manual re-authentication every 7 days. We'd "fixed" it before by simply re-authing, but that was a band-aid. The root cause persisted in our Google Cloud infrastructure configuration. After this latest expiration cycle, we finally traced it to the source and implemented a permanent fix by transitioning our OAuth consent screen from Testing mode to Production mode.

The Problem: Testing Mode Token Expiration

Our Google Cloud project's OAuth consent screen was configured in Testing mode, which imposed a hard limit on refresh token lifetime: 7 days maximum. This meant that every week, our Lambda functions, Apps Script deployments, and CLI tools would lose authorization, requiring manual token refresh. For a production system managing live booking integrations and calendar synchronization, this was unacceptable.

The symptoms were clear:

  • Lambda calendar sync failing with token expiration errors
  • Apps Script deployments unable to write to Google Calendar
  • Boatsetter and Sailo iCal sync triggers blocked
  • Manual intervention required weekly to re-auth via /Users/cb/Documents/repos/tools/reauth_jada_all.py

Previous "fixes" involved running the reauth script and pushing updated tokens to Lambda and Lightsail servers, but the underlying issue—that Testing mode invalidates tokens after 7 days—meant we'd be back here in a week.

Root Cause: OAuth Consent Screen Configuration

Google Cloud's OAuth consent screen has two modes:

  • Testing Mode: Allows unrestricted scope requests but limits refresh tokens to 7 days. Designed for development only.
  • Production Mode: Requires verification and formal configuration but grants indefinite refresh token lifetime. Suitable for deployed applications.

Our project was stuck in Testing mode because we'd never completed the Production transition. The consent screen configuration was accessible only through the Google Cloud Console UI (not via API), which meant we had to navigate the Console directly.

Verification and Prerequisites

Before making changes, we verified our setup:

gcloud auth list
gcloud config get-value project
gcloud iam service-accounts list

We confirmed that:

  • gcloud CLI was installed and authenticated
  • The active project matched our booking automation infrastructure
  • Service accounts and API enablement were in place
  • The OAuth 2.0 Client ID (Web application type) was configured with the correct redirect URIs for our Apps Script deployment and Lambda endpoints

Moving to Production Mode: The Configuration Steps

The OAuth consent screen transition involves several steps in the Google Cloud Console:

  1. Navigate to APIs & Services > OAuth consent screen in the Cloud Console
  2. Update the app information:
    • App name: "JADA Sailing Booking Automation"
    • User support email: verified domain email
    • Developer contact: team contact information
  3. Review requested scopes: Our application requires:
    • https://www.googleapis.com/auth/calendar — read/write calendar events
    • https://www.googleapis.com/auth/gmail.readonly — read inbox for booking notifications
    • https://www.googleapis.com/auth/spreadsheets — update crew dispatch and booking data
    • https://www.googleapis.com/auth/script.external_request — outbound HTTP requests from Apps Script
  4. Configure the brand: Set the official website URL and privacy policy link (must be HTTPS and accessible)
  5. Add authorized domains: Whitelist domains for token issuance (our Lightsail domain and Apps Script deployment domain)
  6. Request verification: Submit for Production mode. Google will verify application details and data handling practices.

Preparing for Verification

Google's verification process checks that applications handle user data responsibly. We prepared:

  • Privacy Policy: Hosted at a static URL explaining how we handle calendar and booking data
  • Data retention policy: Documentation that OAuth tokens are stored securely and user data is only accessed for booking operations
  • Redirect URI verification: Confirmed all OAuth redirect URIs matched deployed endpoints:
    • Apps Script web app deployment URL (from clasp deployments)
    • Lambda API Gateway endpoint
    • Local development callback (for testing)

Token Management After Production Approval

Once Production mode is approved, refresh tokens issued via the OAuth flow will no longer expire after 7 days. However, this doesn't invalidate existing short-lived tokens. We need to:

  1. Trigger a fresh OAuth flow through our reauth script to obtain a new long-lived refresh token
  2. Push updated tokens to Lambda environment variables and Lightsail server token files
  3. Verify sync triggers are activated in Apps Script (specifically calendarDashboardSetup() which creates the time-driven trigger for Boatsetter iCal sync)
  4. Test calendar write operations end-to-end across all platforms (Boatsetter, Sailo, Viator)

Infrastructure Updates Required

After token refresh, we'll update:

  • Lambda environment variables: Re-push refresh token via AWS Secrets Manager or Lambda environment config
  • Lightsail token files: Sync updated tokens via scp to /home/ubuntu/tokens/ directory on the calendar sync server
  • Apps Script deployment: Ensure BookingAutomation.gs (deployed version) has the setup functions triggered via the Apps Script API

Why This Matters for Production

Moving to Production mode eliminates the operational burden of weekly token refresh. For our booking system, this means:

  • Reliability: Calendar sync runs continuously without auth interruptions
  • Automation: Boatsetter instant bookings propagate to JADA Internal calendar without manual intervention
  • Scale: No service degradation as booking volume increases
  • Team efficiency: Engineers spend time on feature work, not OAuth troubleshooting

What's Next

After completing the Google Cloud Console configuration and receiving Production mode approval:

  1. Execute fresh OAuth flow to obtain long-lived refresh token
  2. Update Lambda and Apps Script deployments with new token
  3. Activate calendar sync triggers across all platforms
  4. Monitor sync for 48 hours to confirm stability
  5. Document the final token configuration in our infrastructure runbook

This permanent fix eliminates the recurring 7-day