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 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 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.
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 — 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.
gcloud projects create my-app-drive-prod --name="My App Drive Prod"
gcloud services enable drive.googleapis.com --project=my-app-drive-prod
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 are worth a read — especially the deprecation notices on older features.