I Wasted 3 Days on Google Drive 'Open With' OAuth — Here's Everything I Learned A developer building RenomeeAI, an AI-powered Google Drive document-processing tool, spent three days resolving a 400 redirect_uri_mismatch error in the Drive "Open With" OAuth flow after moving from an ngrok-based local setup to production. The writeup traces the full OAuth and Drive API flow and identifies the production configuration fix that resolved the mismatch. Three days. That's how long it took me to fix a single OAuth error that, in hindsight, had a five-minute solution. The catch? The fix was in a place I never thought to look. If you're building a Google Drive "Open With" App and you're seeing: Error 400: redirect uri mismatch ...this post is for you. I'll walk through exactly how to diagnose it, why the usual advice doesn't work, and the specific steps that actually fixed it. I was building an AI powered document processing tool RenomeeAI https://renomeeai.com/rename-files/pdf/ that integrates with Google Drive. The idea: users right-click a file in Drive → "Open With" → my app → OAuth flow → process the file. Here's the full flow: ┌──────────────┐ ┌─────────────────────┐ ┌──────────────┐ │ Google Drive │ │ drive-api.renomeeai │ │ Google APIs │ │ client │ │ FastAPI │ │ │ └──────┬───────┘ └──────────┬───────────┘ └──────┬───────┘ │ │ │ │ 1. Right-click → "Open │ │ │ With" — Drive builds │ │ │ OAuth URL │ │ │──────────────────────────►│ │ │ │ │ │ │ 2. Redirect → Google auth │ │◄───────────────────────────┼────────────────────────────│ │ User clicks Allow │ │ │ │ 3. callback: code → token │ │ │───────────────────────────►│ │ │◄───────────────────────────│ │ │ access token+refresh token│ │ │ │ │ │ 4. Encrypt → DB, set Cookie│ │◄───────────────────────────│ │ │ │ │ │ 5. Frontend: session/init │ │ │──────────────────────────►│ │ │ │ 6. Fetch file metadata │ │ │───────────────────────────►│ │ │◄───────────────────────────│ │◄───────────────────────────│ files: name, mimeType, │ │ { needs auth, files } │ imageMetadata │ │ │ │ │ 7. User enters rename cmd │ │ │──────────────────────────►│ │ │ │ 8. Call RenameAI │ │ │──────► api.renameai.com │ │ │◄────── suggested name │ │◄───────────────────────────│ │ │ { suggested name } │ │ │ │ │ │ 9. User confirms → apply │ │ │──────────────────────────►│ │ │ │ 10. PATCH Drive filename │ │ │───────────────────────────►│ │◄───────────────────────────│◄───────────────────────────│ │ { success, new name } │ │ During local development, I used ngrok https://ngrok.com/ to expose my local server. I registered my ngrok URL in Google Cloud Console as the OAuth redirect URI and everything worked fine. When I moved to production, I: .env file Then I tested the "Open With" flow and got: Error 400: redirect uri mismatch The redirect URI in the request did not match a registered redirect URI. Request details: redirect uri: https://7886-45-78-56-186.ngrok-free.app/drive/open That's my old ngrok URL . From months ago. It didn't make any sense. This is the part that trips up almost everyone, including me. There are two completely separate OAuth flows in a Drive integration: | Flow | Who builds the OAuth URL | Where redirect uri comes from | |---|---|---| | Your /auth/start route | Your server | Your .env or config | | Drive "Open With" native flow | Google Drive's servers | Drive UI Integration settings | The URL I saw in the Network tab — the one with my old ngrok address — was constructed by Google's servers , not mine. My code had nothing to do with it. My .env was irrelevant. No matter how many times I restarted my container, the URL would keep using whatever was stored in Google's backend registry for my app. Once I understood this, everything else clicked. redirect uri from Chrome DevTools Before changing anything, get the ground truth. accounts.google.com The URL you're looking for looks like this: https://accounts.google.com/o/oauth2/auth ?scope=https://www.googleapis.com/auth/drive.file &client id=YOUR CLIENT ID.apps.googleusercontent.com &response type=code &access type=offline &redirect uri=https://7886-45-78-56-186.ngrok-free.app/drive/open ← the problem &state=%7B%22ids%22%3A%5B%22xxx%22%5D%2C%22action%22%3A%22open%22%2C%22userId%22%3A%22xxx%22%7D Pay attention to the redirect uri parameter. If it's an old ngrok address or any domain you no longer use — this URL was built by Google's servers , not yours. python python3 -c " import urllib.parse as u url = 'PASTE FULL URL HERE' qs = u.parse qs u.urlparse url .query print 'redirect uri:', qs.get 'redirect uri', 'not found' 0 " If you see an old domain, a ngrok address, or anything that's not your current production URL — that's coming from Google's app registry, not your server. Keep reading. Google Cloud Console shows you what you submitted . The Drive API's apps.get endpoint shows you what Drive is actually using . These can diverge significantly, especially if your app was configured months ago. https://www.googleapis.com/auth/drive.apps.readonly TOKEN="paste your access token" APP ID="YOUR PROJECT NUMBER" Your GCP project number all digits , NOT project ID curl -s \ -H "Authorization: Bearer $TOKEN" \ "https://www.googleapis.com/drive/v3/apps/$APP ID?fields=id,name,openUrlTemplate,installed,authorized" \ | python3 -m json.tool Note: APP ID is your project number all digits , not the project ID string. Get it with: gcloud projects describe YOUR PROJECT ID --format="value projectNumber " This is where I found the smoking gun. The openUrlTemplate field looked like this: "openUrlTemplate": "https://accounts.google.com/o/oauth2/auth?scope=...&redirect uri=https://7886-45-78-56-186.ngrok-free.app/drive/open&..." Google had literally hard-coded my old ngrok URL inside the OAuth template that Drive uses to build authorization requests. This template lives server-side at Google. Changing my GCP Console settings didn't update it. Navigate to: Google Cloud Console → APIs & Services → Enabled APIs & Services → Google Drive API → Drive UI Integration tab Look for this checkbox: ☐ Automatically show OAuth 2.0 consent screen when users open my application from Google Drive This was checked on my app. This is where the wrong redirect uri came from in the first place. When this option is enabled, Drive doesn't send users to your Open URL directly. Instead, it builds an OAuth wrapper URL — using the redirect uri you had configured at the time you first enabled this option — and writes that wrapped URL into the Drive App Registry as your app's openUrlTemplate . That value doesn't update automatically when you later change your Redirect URI in GCP Console. Google has deprecated this option https://developers.google.com/drive/api/guides/enable-sdk — but plenty of older tutorials still recommend enabling it. Don't. Step one: Uncheck it. Fix your Open URL. Save. Open URL: https://your-production-domain.com/drive/open ⚠️ Important: unchecking is necessary, but not sufficient. These are two separate problems. The checkbox controls whether Drive continues to generate OAuth wrapper URLs. The Registry cache controls what URL Drive is currently using . Unchecking the box stops the bleeding — it doesn't automatically flush the old openUrlTemplate that's already stored in the Registry. You must verify via apps.get that the Registry has actually updated. Don't trust the Console page. After saving, re-run the apps.get query from Step 2. Check every 10 minutes. What you want to see: "openUrlTemplate": "https://your-production-domain.com/drive/open?state={ids}&action={action}&userId={userId}" No more OAuth wrapper URL. No more old redirect URIs. If it's still showing the old value after 1 hour, stop waiting and go straight to "When Nothing Works" below. The Registry for an old project may simply never sync. Waiting longer won't change that. This one bit me a second time. Even after the registry showed the correct openUrlTemplate , my OAuth flow was still broken — because I had previously installed the app under my test account with the old configuration, and that installation was cached. The correct re-install sequence: apps.get : installed: false If you skip step 4 and re-authorize before the registry updates, you'll re-install the old configuration and be back where you started. I also needed to add image support to my app. I added the MIME types in Console, but after re-installing, images still didn't show up in the "Open With" menu. The issue: existing app installations don't pick up new MIME type configurations . You have to fully uninstall and reinstall following the sequence above . Also, put file types under Secondary rather than Default: Secondary MIME Types: application/pdf image/jpeg image/png image/webp Secondary File Extensions: pdf, jpg, jpeg, png, webp Setting types as "Default" makes your app the default opener, which is usually not what you want. If you've done everything above and apps.get still returns the old openUrlTemplate after an hour, your project's app registry entry may be in a broken state. Google's backend caching for openUrlTemplate can sometimes get stuck. Option A: Disable and re-enable the Drive API gcloud services disable drive.googleapis.com --project=YOUR PROJECT ID gcloud services enable drive.googleapis.com --project=YOUR PROJECT ID Then re-save your Drive UI Integration settings. Sometimes this forces a registry refresh. Not guaranteed. Option B: Create a new GCP project recommended — and what actually fixed it for me After nearly a full day on the old project, I created a new one. The very first apps.get query returned the correct openUrlTemplate . The whole "Open With" flow worked on the first attempt. In hindsight, the time I spent waiting for the old project's Registry to sync was entirely wasted — a new project has no historical cache at all. Create the new project gcloud projects create my-app-drive-prod --name="My App Drive Prod" Enable Drive API gcloud services enable drive.googleapis.com --project=my-app-drive-prod Get the project number for apps.get queries gcloud projects describe my-app-drive-prod --format="value projectNumber " Configure the new project from scratch: Update your server's .env : GOOGLE DRIVE CLIENT ID=new client id GOOGLE DRIVE CLIENT SECRET=new client secret Rebuild your container: docker compose up -d --force-recreate app redirect uri mismatch error | v F12 → Network → copy OAuth URL → extract redirect uri | v Is redirect uri wrong old ngrok, wrong domain ? | YES v OAuth Playground → get token → GET /drive/v3/apps/{appId} | v Does openUrlTemplate contain an OAuth wrapper URL? | YES v Drive API → Drive UI Integration tab → UNCHECK "Automatically show OAuth 2.0 consent screen" → Fix Open URL → Save | v Check apps.get every 10 min → is openUrlTemplate updated? | | NO 1 hour YES | | v v Create new GCP project Uninstall app → wait for apps.get to show installed:false → reinstall GET /drive/v3/apps/{appId} If this saved you some time, the Google Drive API docs on UI Integration https://developers.google.com/drive/api/guides/enable-sdk are worth a read — especially the deprecation notices on older features.