Complete Guide: Google Sign-In with React Native & Supabase (2026)
- Auth
- Supabase
- OAuth
- Security
To add Google Sign-In to an Expo app that uses Supabase, create a Web OAuth client and an iOS client (plus an Android client with your app's SHA-1) in Google Cloud, install @react-native-google-signin/google-signin in a development build, and pass the ID token from GoogleSignin.signIn() to supabase.auth.signInWithIdToken({ provider: 'google', token }). In Supabase, list every client ID in the Google provider with the web client first, and turn on Skip nonce check for iOS.
This guide walks through each piece in order, with code that matches the current library API (the signIn() response shape changed in recent major versions, so a lot of older snippets no longer compile).
Why you need more than one OAuth client
Google issues a separate client ID per platform, and each has a job:
- Web client: its ID goes into
GoogleSignin.configure({ webClientId }). Without it you get noidToken. Its client secret goes into Supabase if you also use the browser OAuth flow. - iOS client: tied to your bundle ID. The iOS SDK uses it, and its reversed ID is the URL scheme your app must register.
- Android client: tied to your package name and signing certificate's SHA-1. You never pass it in code, but sign-in fails on Android if it doesn't exist.
Mixing these up causes most Google Sign-In bugs. Supabase checks the token's audience against its Client IDs list, so every ID that can mint a token has to be there.
What you need first
- A Google Cloud project. Creating OAuth clients doesn't need billing turned on.
- A Supabase project with the Google provider available (it is on every plan).
- An Expo development build. The library contains native code and can't run in Expo Go. Build with
eas build --profile developmentornpx expo run:ios. - Your bundle ID and Android package name settled, since the OAuth clients are tied to them.
Step 1: Set up the Google Auth Platform
Google moved the old "OAuth consent screen" pages into the Google Auth Platform console. You'll fill in four sections:
- Branding: the app name and support email, plus a logo and links to your privacy policy and terms. Brand verification is optional for basic scopes but can take a few business days if you want your logo shown.
- Audience: choose External for a consumer app. While the app is in Testing, only the test users you add here can sign in. Switch it to In production before launch.
- Data Access: add
openidby hand..../auth/userinfo.emailand.../auth/userinfo.profileare there by default. Adding sensitive scopes can trigger a long Google review, so stop at these three unless you need more. - Clients: where you create the client IDs in Step 2.
You don't need to enable any API for sign-in itself. Older guides say to enable the Google+ API. Google shut it down years ago, so skip that step.
Step 2: Create the client IDs
Web application client
- In Clients, click Create client and choose Web application.
- If you also want browser-based Google sign-in, add
https://<project-ref>.supabase.co/auth/v1/callbackunder Authorized redirect URIs. The Supabase dashboard shows the exact URL. - Save the client ID and client secret.
iOS client
- Create another client and choose iOS.
- Enter your bundle ID. Add the App Store ID and Team ID once the app is published.
- Copy the client ID and the iOS URL scheme (it starts with
com.googleusercontent.apps.).
Android client
- Create a client and choose Android.
- Enter your package name and the SHA-1 fingerprint of the certificate that signs the build.
- Create one Android client per signing certificate. You'll usually need three: your debug keystore, the EAS upload keystore (
eas credentials -p androidprints its SHA-1), and Google Play's app signing key, shown on the App integrity page in Play Console.
Step 3: Configure Supabase
- Open Authentication → Providers → Google and enable it.
- In Client IDs, enter all of them separated by commas, with the web client ID first:
WEB_ID,IOS_ID,ANDROID_ID. - Paste the web client's secret into Client Secret.
- Turn on Skip nonce check. Supabase's React Native guide says to enable it for iOS sign-in.
Step 4: Install the library and config plugin
npx expo install @react-native-google-signin/google-signin
If you don't use Firebase, pass the iOS URL scheme to the config plugin:
{
"expo": {
"plugins": [
[
"@react-native-google-signin/google-signin",
{ "iosUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID" }
]
]
}
}
If you use Firebase, list the plugin without options and point ios.googleServicesFile and android.googleServicesFile at GoogleService-Info.plist and google-services.json instead. Either way, rebuild the native app afterwards (npx expo prebuild --clean for local builds, or a new EAS build).
The free package on npm is what the maintainers call the Original module. On Android it uses Google's legacy sign-in SDK, which Google has deprecated but which still works. They sell a premium Universal module built on Android's Credential Manager if you want One Tap and newer flows.
Step 5: Configure Google Sign-In once at startup
// lib/google.ts
import { GoogleSignin } from '@react-native-google-signin/google-signin';
GoogleSignin.configure({
webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID, // required for idToken
iosClientId: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
});
Leave out offlineAccess and forceCodeForRefreshToken unless your own server needs to call Google APIs for the user. Supabase manages its own session and refresh token; it doesn't need Google's.
Client IDs aren't secrets, so EXPO_PUBLIC_ variables are fine here. The web client secret never goes in the app.
Step 6: Build the sign-in button
// components/GoogleSignInButton.tsx
import { useState } from 'react';
import { Alert } from 'react-native';
import {
GoogleSignin,
GoogleSigninButton,
isErrorWithCode,
isSuccessResponse,
statusCodes,
} from '@react-native-google-signin/google-signin';
import { supabase } from '../lib/supabase';
import '../lib/google';
export function GoogleSignInButton() {
const [busy, setBusy] = useState(false);
const signIn = async () => {
setBusy(true);
try {
await GoogleSignin.hasPlayServices(); // Android only; resolves on iOS
const response = await GoogleSignin.signIn();
if (!isSuccessResponse(response)) return; // user cancelled
const idToken = response.data.idToken;
if (!idToken) throw new Error('No idToken. Is webClientId set?');
const { error } = await supabase.auth.signInWithIdToken({
provider: 'google',
token: idToken,
});
if (error) throw error;
} catch (error) {
if (isErrorWithCode(error)) {
if (error.code === statusCodes.IN_PROGRESS) return;
if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
Alert.alert('Google Play Services is missing or out of date.');
return;
}
}
Alert.alert('Google sign-in failed', String(error));
} finally {
setBusy(false);
}
};
return (
<GoogleSigninButton
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={signIn}
disabled={busy}
/>
);
}
Since the library's newer major versions, signIn() resolves with { type, data } instead of throwing on cancel. Use isSuccessResponse and read the token from response.data.idToken. Code that reads userInfo.idToken directly is written for the old API.
Step 7: Sign out and check the current user
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { supabase } from '../lib/supabase';
export async function signOut() {
await supabase.auth.signOut();
await GoogleSignin.signOut(); // so the account picker shows next time
}
// Synchronous helpers in the current API:
const signedInBefore = GoogleSignin.hasPreviousSignIn();
const googleUser = GoogleSignin.getCurrentUser(); // null if none
Your app's "is the user logged in" check should read the Supabase session, not Google's state. Supabase keeps its session in storage and refreshes it on its own, so you don't have to call signInSilently() to keep anyone logged in. If you followed the Sign in with Apple guide, the same useSession hook works here unchanged.
Verifying tokens on your own server
Supabase checks the token's signature and audience for you. If you also send Google ID tokens to your own API, verify them there with Google's library and accept every client ID you issued:
// Node.js
const { OAuth2Client } = require('google-auth-library');
const client = new OAuth2Client();
async function verifyGoogleToken(idToken) {
const ticket = await client.verifyIdToken({
idToken,
audience: [WEB_CLIENT_ID, IOS_CLIENT_ID, ANDROID_CLIENT_ID],
});
return ticket.getPayload(); // payload.sub is the stable Google user ID
}
Troubleshooting
DEVELOPER_ERROR on Android
The package name or SHA-1 doesn't match any Android client, or webClientId is wrong. Check which certificate signed the build you're running, then make sure that exact SHA-1 has its own Android client.
The iOS app crashes with "missing support for the following URL schemes"
The reversed iOS client ID isn't registered. Set iosUrlScheme in the config plugin (or add the Firebase plist) and rebuild.
idToken is null
webClientId is missing or points at a non-web client.
"Unacceptable audience in id_token"
The client ID that minted the token isn't in Supabase's Client IDs list.
Nonce errors on iOS
Turn on Skip nonce check in the Supabase Google provider.
Only some people can sign in
The Audience is still set to Testing, so only listed test users get through. Publish the app to production.
Useful commands
# Show signing credentials, including the SHA-1, for EAS builds
eas credentials -p android
# Print the resolved app config, including URL schemes
npx expo config --type introspect
# Build and run on a connected iPhone
npx expo run:ios --device
Before you launch
- Set the Audience to In production and finish Branding, including the privacy policy link.
- Add the Play app signing SHA-1 as its own Android client before your first Play release, or sign-in will fail only in store builds.
- On iOS, Guideline 4.8 means an app with Google Sign-In also needs an equivalent private login option such as Sign in with Apple. See our Sign in with Apple guide.
- Describe Google account data in your privacy policy and App Privacy answers.
Where AppSetUpKit fits
AppSetUpKit doesn't ship a sign-in button or auth state code. Its setup wizard creates a Firebase project with iOS and Android apps, downloads GoogleService-Info.plist and google-services.json into your project, enables the Identity Toolkit API, and installs @react-native-google-signin/google-signin with its config plugin. You still create the OAuth client IDs in Google Cloud yourself (the wizard tells you where), and the kit's supabase-auth-setup.sh then writes the client ID and secret into Supabase's Google provider through the Management API. See what's in the kit.
More guides
Expo App Store Launch Checklist (2026)
Every step between a finished Expo app and Submit to App Review, in order, with the Apple rule or Expo doc behind each one. Checked against Apple's and Expo's documentation on November 3, 2026.
AppSetUpKit vs Expo Boilerplates: Free and Paid Starters Compared (2026)
Boilerplates give you app code, while AppSetUpKit sets up the accounts around it. We compare the free Expo starters with four paid ones (prices as listed on November 3, 2026) and say when a free option is enough.