Skip to content

Google OAuth on React Native

Native OAuth with Expo WebBrowser

On the web, Google OAuth uses a popup. Native apps don't have popups — instead, the SDK opens the system auth browser and returns to your app via a deep link. The useAuthenticateOAuthWithExpoWebBrowser hook handles both ends of that round trip on both platforms: the auth session (ASWebAuthenticationSession on iOS, Custom Tabs on Android) intercepts the custom-scheme redirect itself, so no domain setup is needed.

1. Install peer dependencies

Install the peer deps (already present in the default Expo starter):

npm
npx expo install expo-linking expo-web-browser

2. Add a deep-link scheme

Make sure a custom scheme is defined in app.json (deep link back into the app):

{
  "expo": {
    "scheme": "<your custom scheme>",
  },
}

3. Add the sign-in component

import { OAUTH_PROVIDERS } from "@zerodev/wallet-react";
import { useAuthenticateOAuthWithExpoWebBrowser } from "@zerodev/wallet-react/react-native/oauth/with-expo-web-browser";
import * as Linking from "expo-linking";
import { Button, Text, View } from "react-native";
import { useAccount } from "wagmi";
 
/** Renders nothing once the wallet is connected. */
export function GoogleOauthFlow() {
  const { status } = useAccount();
  const auth = useAuthenticateOAuthWithExpoWebBrowser({
    redirectUri: Linking.createURL("oauth-callback"),
  });
 
  if (status === "connected") return null;
 
  return (
    <View style={{ gap: 8, padding: 16, borderWidth: 1, borderRadius: 8 }}>
      <Text style={{ fontWeight: "600" }}>Sign in (Google)</Text>
      <Button
        title={auth.isPending ? "Signing in..." : "Continue with Google"}
        disabled={auth.isPending}
        onPress={() => auth.mutate({ provider: OAUTH_PROVIDERS.GOOGLE })}
      />
      {auth.error ? (
        <Text style={{ color: "red" }}>{auth.error.message}</Text>
      ) : null}
    </View>
  );
}

See the useAuthenticateOAuthWithExpoWebBrowser reference for the full API.

4. Add the callback route

Add an OAuth callback route, e.g. app/oauth-callback.tsx:

import { Redirect } from "expo-router";
 
/**
 * Catches the OAuth deep-link redirect (`<scheme>://oauth-callback?...`) so
 * Expo Router doesn't show "Unmatched Route". The ZeroDev SDK reads the
 * session_id from the URL via its own `Linking` listener, so this screen
 * just needs to bounce back to /.
 */
export default function OAuthCallback() {
  return <Redirect href="/" />;
}

This route is for the native OAuth flow. If the same Expo app also runs on web, the .web variant should use useAuthenticateOAuth and return to the current page instead of relying on app/oauth-callback.tsx.

5. Allowlist the redirect URL

On the ZeroDev Dashboard, allowlist the redirect URL. After a successful sign-in the browser redirects back into the app.

Using a verified https link as the redirect

Once the Domain Association is set up on your domain, you can use a verified https link (https://<your domain>/oauth-callback) instead of the custom scheme.

The two platforms deliver this redirect through different mechanisms:

  • Android delivers it as a verified App Link: the OS hands the redirect to your app as an intent (Expo guide). Requires an intent filter (below).
  • iOS 17.4+ resolves it inside the auth session itself: ASWebAuthenticationSession observes the redirect to your domain and returns it to the SDK. This is not Universal Link routing — iOS only opens Universal Links from a user tap, never from the server-side redirect that ends an OAuth flow — so the AASA doesn't need to claim the /oauth-callback path. What it does require is the webcredentials:<your domain> entitlement (the same one passkeys use) from the Domain Association setup.

Android: add an intent filter

Add an intentFilter for /oauth-callback in app.json:

{
  "expo": {
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [
            {
              "scheme": "https",
              "host": "<your domain>",
              "pathPrefix": "/oauth-callback",
            },
          ],
          "category": ["BROWSABLE", "DEFAULT"],
        },
      ],
    },
  },
}

After app.json changes, regenerate the native project: npx expo prebuild --clean, then rebuild. Intent filters land in AndroidManifest.xml and App Link verification happens at install time.

iOS: check the requirements

No OAuth-specific setup beyond the Domain Association — but three things must hold:

  • The build carries the webcredentials:<your domain> entitlement (paid Apple Developer team). Entitlement changes only land after npx expo prebuild --clean and a rebuild.
  • The hosted apple-app-site-association lists your <team id>.<bundle id> under webcredentials. If the entitlement or AASA is missing or stale, the flow strands the user on the redirect page inside the auth sheet — during development, keep the ?mode=developer flag on the entitlement so the device fetches the AASA straight from your origin instead of Apple's CDN.
  • The device runs iOS 17.4 or later. Older versions can't observe https callbacks in the auth session — fall back to the custom scheme there (snippet below).

Pass the https redirect

import { RP_ID } from "@/wagmi.config";
// ...
const auth = useAuthenticateOAuthWithExpoWebBrowser({
  redirectUri: `https://${RP_ID}/oauth-callback`,
});

The hook detects the https redirect and configures the auth session accordingly — no extra option needed on either platform.

To keep older iOS versions working, pick the redirect per platform and version:

import * as Linking from "expo-linking";
import { Platform } from "react-native";
 
import { RP_ID } from "@/wagmi.config";
 
// iOS can only observe https auth-session callbacks on 17.4+.
const supportsHttpsRedirect =
  Platform.OS !== "ios" || Number.parseFloat(String(Platform.Version)) >= 17.4;
 
const auth = useAuthenticateOAuthWithExpoWebBrowser({
  redirectUri: supportsHttpsRedirect
    ? `https://${RP_ID}/oauth-callback`
    : Linking.createURL("oauth-callback"),
});

Since these are new redirect URLs, remember to allowlist them on the Dashboard as well.