The moment a tester taps "Sign in with Google" and the screen explodes into Error 400: redirect_uri_mismatch — if you have wired OAuth into a Rork app, you have probably seen that red screen at least once. After running an indie app business with over 50 million cumulative downloads, I still spend 30 to 60 minutes per project lining up Google Sign-In whenever I add it to a fresh codebase.
Rork-generated apps make this trickier than usual because they assume the Expo Managed Workflow, which hides native URL Schemes and the Android SHA-1 certificate dance behind generated config. Asking the AI to "just fix it" only updates code; if Google Cloud Console stays out of sync, you can rebuild forever and still get the same red screen.
This guide walks through 5 checkpoints that pinpoint why redirect_uri_mismatch appears in a Rork project, with the exact commands and config snippets to use along the way.
Why redirect_uri_mismatch is so common in Rork projects
Behind the scenes, Google opens a browser (or in-app browser) for the user, and on success redirects back to a URI that you registered ahead of time. Google then verifies that the redirect URI matches exactly what is stored in the OAuth client. A single character off and you get redirect_uri_mismatch.
In Rork apps, the redirect URI is auto-generated by expo-auth-session. The trap is that this URI changes per environment:
- Expo Go (preview):
https://auth.expo.io/@your-username/your-slug - EAS development build:
your.bundle.id:// - iOS production build:
your.bundle.id://oauthredirect - Android production build:
your.bundle.id:/oauth2redirect/google(note the single colon)
That is why "it worked in preview but TestFlight breaks" happens so often. You probably only registered the preview URI in Google Cloud Console. I lost half a day to this on my first project before noticing the pattern.
Check 1: Create all three OAuth clients in Google Cloud Console
A frequently missed detail: a fully working Google Sign-In flow needs three OAuth clients, not one or two.
- iOS client (application type: iOS)
- Android client (application type: Android)
- Web client (application type: Web application)
It feels like the Web client should be optional in a mobile-only project, but expo-auth-session's Google.useAuthRequest internally needs the Web client ID. Without it, Expo Go preview fails outright and certain iOS flows silently break.
Under your Web client, add at least these two entries to "Authorized redirect URIs":
https://auth.expo.io/@your-expo-username/your-app-slug
https://your.bundle.id/oauth2redirect
Replace your-expo-username and your-app-slug with the actual values from your app.json (owner and slug fields). Rork generates a slug automatically, so always open app.json and copy from there rather than typing from memory.
Check 2: Match Bundle ID and Package Name exactly
The "Bundle ID" in the iOS client and the "Package name" in the Android client must match app.json (or app.config.js) exactly — including case and dot placement.
// app.json
{
"expo": {
"ios": {
"bundleIdentifier": "com.yourname.rorkapp"
},
"android": {
"package": "com.yourname.rorkapp"
}
}
}Also check that placeholders like com.example.app did not leak through from Rork's scaffolding. On a project I once inherited, only app.json had been updated; an older Bundle ID lingered in eas.json's prebuild config, so the production build alone failed authentication.
Quick verification one-liners:
# Confirm Bundle ID and Package Name match
grep -E "bundleIdentifier|package" app.json
grep -rE "bundleIdentifier|applicationId|packageName" eas.json android/ ios/ 2>/dev/nullCheck 3: Register the right SHA-1 certificate fingerprints for Android
The single biggest cause of stubborn redirect_uri_mismatch errors on Android is missing or wrong SHA-1 fingerprints. Android treats apps signed with different certificates as different apps, so debug builds and release builds need separate SHA-1 entries.
When using EAS Build, fetch your SHA-1 with:
# From the project root
eas credentials -p androidChoose "Keystore: Manage everything needed to build your project" → "Show keystore credentials". The CLI prints both SHA-1 and SHA-256, which you then paste into the Android client in Google Cloud Console.
Expected output:
SHA1 Fingerprint: A1:B2:C3:D4:E5:F6:...
SHA256 Fingerprint: A1:B2:C3:D4:E5:F6:...
A subtle gotcha: eas build --profile preview (internal distribution) often uses a different keystore from eas build --profile production. Check the SHA-1 for each profile you actually ship and add them all to the Android client.
For my own indie apps I keep eas.json explicit so each profile maps cleanly to its keystore:
{
"build": {
"preview": {
"distribution": "internal",
"android": { "buildType": "apk" }
},
"production": {
"android": { "buildType": "app-bundle" }
}
}
}Check 4: Align Expo's scheme with iOS URL Schemes
On iOS, the system returns to your app via a custom URL scheme after authentication. Three things must agree: scheme in app.json, CFBundleURLSchemes in Info.plist, and the reversed Bundle ID Google Cloud Console gives you on the iOS client.
The reversed Bundle ID looks like this in the iOS client detail view:
com.googleusercontent.apps.123456789012-abcdefghijklmnop
Add it to app.json as follows:
{
"expo": {
"scheme": "rorkapp",
"ios": {
"infoPlist": {
"CFBundleURLTypes": [
{
"CFBundleURLSchemes": [
"com.googleusercontent.apps.123456789012-abcdefghijklmnop"
]
}
]
}
}
}
}Setting both scheme and CFBundleURLSchemes is the key. If Rork-generated code only declares scheme, iOS will succeed at the auth screen but fail to return to your app — leaving the user stranded in Safari.
After editing app.json, always run npx expo prebuild --clean to regenerate ios/, then rebuild with eas build. Preview may still appear to work, because this setting only takes effect on native builds.
Check 5: Isolate the cause with a minimal repro
If careful auditing of the four checkpoints above still does not fix it, fall back to a minimal expo-auth-session implementation. Whenever I get stuck and lose track of which knob is wrong, I always return to this pattern.
import * as Google from 'expo-auth-session/providers/google';
import * as WebBrowser from 'expo-web-browser';
import { useEffect } from 'react';
import { Button, Text, View, Alert } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
export default function GoogleLoginDebug() {
const [request, response, promptAsync] = Google.useAuthRequest({
iosClientId: 'YOUR_IOS_CLIENT_ID.apps.googleusercontent.com',
androidClientId: 'YOUR_ANDROID_CLIENT_ID.apps.googleusercontent.com',
webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
});
useEffect(() => {
if (response?.type === 'success') {
const { authentication } = response;
Alert.alert('Success', 'Token: ' + authentication?.accessToken?.slice(0, 20));
} else if (response?.type === 'error') {
Alert.alert('Error', JSON.stringify(response.error));
}
}, [response]);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Redirect URI debug</Text>
<Text selectable>{request?.redirectUri}</Text>
<Button
disabled={!request}
title="Login with Google"
onPress={() => promptAsync()}
/>
</View>
);
}Expected behavior: the redirectUri printed on screen exactly matches the redirect URI you have registered in Google Cloud Console.
Copy that displayed URI character-for-character into Google Cloud Console's "Authorized redirect URIs" list and redirect_uri_mismatch disappears for good. I used to eyeball this in the early days and once miscounted dots — copy/paste rather than retyping is a discipline worth keeping.
A reusable Rork prompt to set this up correctly
When the issue refuses to budge, I lean on Rork with a tightly scoped prompt that covers every checkpoint above:
Implement Google Sign-In using the Google provider in expo-auth-session.
Please follow these constraints exactly:
1. Read iosClientId, androidClientId, and webClientId from environment variables.
2. Set the scheme in app.json to 'rorkapp'.
3. Add the reversed Bundle ID to CFBundleURLSchemes on iOS.
4. Show request.redirectUri on the login screen as a debug Text element.
5. When response.type === 'error', surface the details via an Alert.
Item 4 is especially worth keeping in production. If a tester or end-user hits an error, they can screenshot the redirectUri string and send it to you directly — turning a vague "Google Sign-In does not work" report into a one-line diff against your registered URIs.
Wrapping up and next steps
redirect_uri_mismatch happens because the smallest possible gap exists between Google Cloud Console and your Expo configuration. Walking through the five checks in order resolves nearly every case.
The next concrete action: on your current project, verify Google Sign-In end-to-end on all three builds — Expo Go, EAS preview, and EAS production. Discovering a misalignment the night before submission can delay a release by a day or two. Making this a routine habit pays off the day you actually need to ship in a hurry.
If you want to make the rest of your auth stack more resilient, Firebase / Supabase auth error fix guide and Clerk social login implementation guide are good companions. Clerk in particular handles much of the OAuth client wiring through its dashboard, which can save a lot of indie-developer time.
I hope this saves you the hours I spent on it the first time. Wishing you a clean redirect on your next build.