{"slug": "i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned", "title": "I Wasted 3 Days on Google Drive 'Open With' OAuth — Here's Everything I Learned", "summary": "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.", "body_md": "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.\n\nIf you're building a **Google Drive \"Open With\" App** and you're seeing:\n\n```\nError 400: redirect_uri_mismatch\n```\n\n...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.\n\nI 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.\n\nHere's the full flow:\n\n```\n┌──────────────┐         ┌─────────────────────┐         ┌──────────────┐\n│ Google Drive │         │  drive-api.renomeeai │         │  Google APIs │\n│   (client)   │         │     (FastAPI)        │         │              │\n└──────┬───────┘         └──────────┬───────────┘         └──────┬───────┘\n       │                            │                             │\n       │  1. Right-click → \"Open    │                             │\n       │     With\" — Drive builds   │                             │\n       │     OAuth URL              │                             │\n       │──────────────────────────►│                             │\n       │                            │                             │\n       │                            │  2. Redirect → Google auth  │\n       │◄───────────────────────────┼────────────────────────────│\n       │  User clicks Allow         │                             │\n       │                            │  3. callback: code → token  │\n       │                            │───────────────────────────►│\n       │                            │◄───────────────────────────│\n       │                            │   access_token+refresh_token│\n       │                            │                             │\n       │                            │  4. Encrypt → DB, set Cookie│\n       │◄───────────────────────────│                             │\n       │                            │                             │\n       │  5. Frontend: session/init  │                             │\n       │──────────────────────────►│                             │\n       │                            │  6. Fetch file metadata     │\n       │                            │───────────────────────────►│\n       │                            │◄───────────────────────────│\n       │◄───────────────────────────│   files: [name, mimeType,   │\n       │  { needs_auth, files }     │           imageMetadata]    │\n       │                            │                             │\n       │  7. User enters rename cmd  │                             │\n       │──────────────────────────►│                             │\n       │                            │  8. Call RenameAI           │\n       │                            │──────► api.renameai.com     │\n       │                            │◄──────  suggested_name      │\n       │◄───────────────────────────│                             │\n       │  { suggested_name }        │                             │\n       │                            │                             │\n       │  9. User confirms → apply  │                             │\n       │──────────────────────────►│                             │\n       │                            │  10. PATCH Drive filename   │\n       │                            │───────────────────────────►│\n       │◄───────────────────────────│◄───────────────────────────│\n       │  { success, new_name }     │                             │\n```\n\nDuring 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.\n\nWhen I moved to production, I:\n\n`.env` file\nThen I tested the \"Open With\" flow and got:\n\n```\nError 400: redirect_uri_mismatch\nThe redirect URI in the request did not match a registered redirect URI.\n\nRequest details:\nredirect_uri: https://7886-45-78-56-186.ngrok-free.app/drive/open\n```\n\nThat's my **old ngrok URL**. From months ago. It didn't make any sense.\n\nThis is the part that trips up almost everyone, including me.\n\nThere are two completely separate OAuth flows in a Drive integration:\n\n| Flow | Who builds the OAuth URL | Where `redirect_uri` comes from | \n|---|---|---|\n| Your `/auth/start` route | **Your server** | Your `.env` or config | \n| Drive \"Open With\" native flow | **Google Drive's servers** | **Drive UI Integration settings** | \n\nThe 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.\n\nOnce I understood this, everything else clicked.\n\n`redirect_uri` from Chrome DevTools\nBefore changing anything, get the ground truth.\n\n`accounts.google.com`\nThe URL you're looking for looks like this:\n\n```\nhttps://accounts.google.com/o/oauth2/auth\n  ?scope=https://www.googleapis.com/auth/drive.file\n  &client_id=YOUR_CLIENT_ID.apps.googleusercontent.com\n  &response_type=code\n  &access_type=offline\n  &redirect_uri=https://7886-45-78-56-186.ngrok-free.app/drive/open   ← the problem\n  &state=%7B%22ids%22%3A%5B%22xxx%22%5D%2C%22action%22%3A%22open%22%2C%22userId%22%3A%22xxx%22%7D\n```\n\nPay 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.\n\n``` python\npython3 -c \"\nimport urllib.parse as u\nurl = 'PASTE_FULL_URL_HERE'\nqs = u.parse_qs(u.urlparse(url).query)\nprint('redirect_uri:', qs.get('redirect_uri', ['not found'])[0])\n\"\n```\n\nIf 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.\n\nGoogle 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.\n\n```\n   https://www.googleapis.com/auth/drive.apps.readonly\nTOKEN=\"paste_your_access_token\"\nAPP_ID=\"YOUR_PROJECT_NUMBER\"  # Your GCP project number (all digits), NOT project ID\n\ncurl -s \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  \"https://www.googleapis.com/drive/v3/apps/$APP_ID?fields=id,name,openUrlTemplate,installed,authorized\" \\\n  | python3 -m json.tool\n```\n\n**Note:** `APP_ID` is your **project number** (all digits), not the project ID string. Get it with:\n\n```\ngcloud projects describe YOUR_PROJECT_ID --format=\"value(projectNumber)\"\n```\n\n**This is where I found the smoking gun.**\n\nThe `openUrlTemplate` field looked like this:\n\n```\n\"openUrlTemplate\": \"https://accounts.google.com/o/oauth2/auth?scope=...&redirect_uri=https://7886-45-78-56-186.ngrok-free.app/drive/open&...\"\n```\n\nGoogle 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.\n\nNavigate to:\n\n```\nGoogle Cloud Console\n  → APIs & Services\n  → Enabled APIs & Services\n  → Google Drive API\n  → Drive UI Integration tab\n```\n\nLook for this checkbox:\n\n```\n☐ Automatically show OAuth 2.0 consent screen when users open my\n  application from Google Drive\n```\n\n**This was checked on my app.** This is where the wrong `redirect_uri` came from in the first place.\n\nWhen 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.\n\nGoogle has [deprecated this option](https://developers.google.com/drive/api/guides/enable-sdk) — but plenty of older tutorials still recommend enabling it. Don't.\n\n**Step one: Uncheck it. Fix your Open URL. Save.**\n\n```\nOpen URL: https://your-production-domain.com/drive/open\n```\n\n⚠️ **Important: unchecking is necessary, but not sufficient.**\n\nThese 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.\n\nYou **must** verify via `apps.get` that the Registry has actually updated. Don't trust the Console page.\n\nAfter saving, re-run the `apps.get` query from Step 2. Check every 10 minutes.\n\n**What you want to see:**\n\n```\n\"openUrlTemplate\": \"https://your-production-domain.com/drive/open?state={ids}&action={action}&userId={userId}\"\n```\n\nNo more OAuth wrapper URL. No more old redirect URIs.\n\n**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.\n\nThis 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.\n\n**The correct re-install sequence:**\n\n`apps.get`:` installed: false`\nIf you skip step 4 and re-authorize before the registry updates, you'll re-install the old configuration and be back where you started.\n\nI 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.\n\nThe issue: **existing app installations don't pick up new MIME type configurations**. You have to fully uninstall and reinstall (following the sequence above).\n\nAlso, put file types under **Secondary** rather than Default:\n\n```\nSecondary MIME Types:\n  application/pdf\n  image/jpeg\n  image/png\n  image/webp\n\nSecondary File Extensions:\n  pdf, jpg, jpeg, png, webp\n```\n\nSetting types as \"Default\" makes your app the default opener, which is usually not what you want.\n\nIf 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.\n\n**Option A: Disable and re-enable the Drive API**\n\n```\ngcloud services disable drive.googleapis.com --project=YOUR_PROJECT_ID\ngcloud services enable drive.googleapis.com --project=YOUR_PROJECT_ID\n```\n\nThen re-save your Drive UI Integration settings. Sometimes this forces a registry refresh. Not guaranteed.\n\n**Option B: Create a new GCP project (recommended — and what actually fixed it for me)**\n\nAfter 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.\n\n```\n# Create the new project\ngcloud projects create my-app-drive-prod --name=\"My App Drive Prod\"\n\n# Enable Drive API\ngcloud services enable drive.googleapis.com --project=my-app-drive-prod\n\n# Get the project number for apps.get queries\ngcloud projects describe my-app-drive-prod --format=\"value(projectNumber)\"\n```\n\nConfigure the new project from scratch:\n\nUpdate your server's `.env`:\n\n```\nGOOGLE_DRIVE_CLIENT_ID=new_client_id\nGOOGLE_DRIVE_CLIENT_SECRET=new_client_secret\n```\n\nRebuild your container:\n\n```\ndocker compose up -d --force-recreate app\nredirect_uri_mismatch error\n         |\n         v\nF12 → Network → copy OAuth URL → extract redirect_uri\n         |\n         v\nIs redirect_uri wrong (old ngrok, wrong domain)?\n         |\n         YES\n         v\nOAuth Playground → get token → GET /drive/v3/apps/{appId}\n         |\n         v\nDoes openUrlTemplate contain an OAuth wrapper URL?\n         |\n         YES\n         v\nDrive API → Drive UI Integration tab\n→ UNCHECK \"Automatically show OAuth 2.0 consent screen\"\n→ Fix Open URL → Save\n         |\n         v\nCheck apps.get every 10 min → is openUrlTemplate updated?\n    |                              |\n    NO (>1 hour)                  YES\n    |                              |\n    v                              v\nCreate new GCP project        Uninstall app → wait for apps.get\n                              to show installed:false → reinstall\n```\n\n`GET /drive/v3/apps/{appId}`\n*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.*", "url": "https://wpnews.pro/news/i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned", "canonical_source": "https://dev.to/hetianhe/i-wasted-3-days-on-google-drive-open-with-oauth-heres-everything-i-learned-1h55", "published_at": "2026-11-12 02:09:42+00:00", "updated_at": "2026-11-12 02:22:07.104838+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Google Drive", "RenomeeAI", "Google Cloud Console", "ngrok", "FastAPI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned", "markdown": "https://wpnews.pro/news/i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned.md", "text": "https://wpnews.pro/news/i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned.txt", "jsonld": "https://wpnews.pro/news/i-wasted-3-days-on-google-drive-open-with-oauth-here-s-everything-i-learned.jsonld"}}