← Back to blog

Firebase Auth Expo: Email, Google, Apple + Routes

Firebase Auth on Expo works well when you treat it as a native-auth problem: email/password is the easy path, Google and Apple need console credentials plus a development build, and Expo Router needs a clear session gate so signed-in and signed-out stacks do not fight each other.

This guide walks through a practical firebase auth expo setup: email/password (and optional email verification), Google Sign-In, Sign in with Apple (iOS), guest/anonymous mode, Expo Router protected-route patterns, and the real error strings that burn hours. Soft note: Auth is already wired in the CodeBaseHub Expo starter kit—you still configure Firebase/Google/Apple credentials and ship a dev build; this is not zero-setup.

Key Takeaways

  • Use a development build (expo-dev-client, npx expo run:*, or EAS). Native Google Sign-In, Apple auth, and @react-native-firebase/* do not belong in Expo Go (Expo Google authentication).
  • Wire email/password, Google, Apple (iOS only), and optional guest/anonymous—then gate navigation with a session provider, not a separate middleware layer.
  • Treat auth/invalid-credential as the modern generic failure for wrong email/password when Email Enumeration Protection is on (Firebase JS Auth reference).
  • Register every Android SHA-1 you ship with (local debug, EAS, Play App Signing) or Google Sign-In will fail on some builds (Expo Google guide).
  • Enable Sign in with Apple capability (ios.usesAppleSignIn) and rebuild; App Store review also expects Apple when you offer other third-party logins (expo-apple-authentication).
  • Prefer Expo Router Stack.Protected (SDK 53+) or a layout-level session redirect; wait for auth to load before redirecting (Expo Router authentication).

What You Need Before Coding

Lock these prerequisites before you expect Google or Apple to work on a device.

1. Firebase project + Auth providers

In the Firebase console:

  • Create (or open) a project and register iOS and Android apps with the same bundle ID / package name as your Expo app config.
  • Enable Email/Password, Google, and Apple under Authentication → Sign-in method.
  • Download GoogleService-Info.plist (iOS) and google-services.json (Android). Point Expo at them via ios.googleServicesFile / android.googleServicesFile (see Expo Google authentication).

2. A development build (not Expo Go)

Libraries that ship custom native code—@react-native-firebase/*, @react-native-google-signin/google-signin (or Nitro Google Sign-In), and production-faithful Apple auth—cannot be validated inside Expo Go. Expo's own Google guide states those libraries need a config plugin and a development build.

Plan on:

  • expo-dev-client in the project
  • npx expo prebuild (when using CNG) + npx expo run:ios / run:android, or EAS Build

3. Google Cloud / SHA-1 and Apple Developer

  • Google (Android): add every SHA-1 you ship with (local, EAS, Play App Signing) in Firebase Project settings (Firebase Android FAQ; Expo Google guide). Keep EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID (web client ID) for ID-token exchange.
  • Apple: enable Sign in with Apple on the App ID, configure Firebase's Apple provider, set ios.usesAppleSignIn: true, rebuild (Firebase Apple; Expo AppleAuthentication).

Firebase Auth on Expo: setup checklist

Use this as a working checklist for firebase auth expo projects (and validate paths against your own repo or kit).

  1. Install / link native auth — Firebase Auth JS or @react-native-firebase/auth, plus Google and Apple packages your stack requires; add config plugins; rebuild the native binary.
  2. Drop config files — google-services.json, GoogleService-Info.plist, and the Google web client ID env var your Google → Firebase credential flow needs.
  3. Enable providers — Email/Password, Google, Apple in Firebase Auth; match package / bundle IDs exactly.
  4. Feature flags — turn providers on deliberately (examples below). Keep email verification off until you want the UX and email templates live.
  5. Auth context + router gate — one session source of truth; session-based redirect between an auth stack and the main app tabs.
  6. Test on a real build — email first, then Google on Android and iOS, then Apple on a physical iPhone / Simulator with capability enabled.

Feature flags you will actually use

In a kit-style Expo app, feature flags keep unfinished console work from shipping as half-broken buttons. A common pattern:

FlagTypical defaultMeaning
googleSignIntrueShow Google button; requires native Google + Firebase Google provider
appleSignIntrue (iOS only)Show Apple button on iOS; hide or no-op on Android
guestModetrueAllow anonymous / guest sign-in
autoGuestModefalseIf ever enabled, can skip the auth stack entirely—keep off unless that UX is intentional
emailVerificationfalseCode may exist; default OFF so you do not force verify-email flows before templates and deep links are ready

Soft CTA: those patterns (email/password, Google, Apple, guest, verification behind a flag) are how Auth is wired in the Expo starter kit. You still own Firebase console setup and a native rebuild.

Email / Password (and Optional Verification)

Email/password is the fastest smoke test. Modular JS shape (React Native Firebase is similar):

import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  sendPasswordResetEmail,
  sendEmailVerification,
} from "firebase/auth";
import { auth } from "./firebase"; // your initialized Auth instance

export async function signUp(email: string, password: string) {
  const cred = await createUserWithEmailAndPassword(auth, email, password);
  // Only if FEATURES.emailVerification === true:
  // await sendEmailVerification(cred.user);
  return cred.user;
}

export async function signIn(email: string, password: string) {
  const cred = await signInWithEmailAndPassword(auth, email, password);
  return cred.user;
}

export async function resetPassword(email: string) {
  await sendPasswordResetEmail(auth, email);
}

Persistence: use React Native Auth persistence (getReactNativePersistence + AsyncStorage/SecureStore). Do not invent a second "logged in" flag that disagrees with onAuthStateChanged.

Email verification: feature flag default OFF. Turn on only when templates, continue URLs, and product rules (block tabs vs soft nudge) are ready.

Password reset: a forgot-password screen calling sendPasswordResetEmail is enough for v1.

Google Sign-In on Expo (Dev Build Required)

Use a native Google library + config plugin + development build—not Expo Go (Using Google authentication):

  1. Install @react-native-google-signin/google-signin or react-native-nitro-google-signin; add the config plugin.
  2. Place google-services.json / GoogleService-Info.plist in app config.
  3. Set webClientId from EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID for an ID token.
  4. GoogleAuthProvider.credential → signInWithCredential.
  5. Rebuild after plugin or Google Services changes.

Credential handoff:

import { GoogleSignin } from "@react-native-google-signin/google-signin";
import { GoogleAuthProvider, signInWithCredential } from "firebase/auth";
import { auth } from "./firebase";

GoogleSignin.configure({
  webClientId: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
});

export async function signInWithGoogle() {
  await GoogleSignin.hasPlayServices();
  const response = await GoogleSignin.signIn();
  const idToken = response.data?.idToken; // shape varies by library version
  if (!idToken) throw new Error("Missing Google ID token");
  const credential = GoogleAuthProvider.credential(idToken);
  return signInWithCredential(auth, credential);
}

Pin library versions carefully; the invariant is native Google ID token → Firebase credential → session.

Sign in with Apple (iOS Only)

Apple auth on Expo uses expo-apple-authentication:

  • Set ios.usesAppleSignIn: true (and the Apple Authentication plugin where applicable).
  • Rebuild so the com.apple.developer.applesignin entitlement is present.
  • Call AppleAuthentication.signInAsync, pass a hashed nonce to Apple and the raw nonce to Firebase (Firebase Authenticate Using Apple).
import * as AppleAuthentication from "expo-apple-authentication";
import * as Crypto from "expo-crypto";
import { OAuthProvider, signInWithCredential } from "firebase/auth";
import { auth } from "./firebase";

export async function signInWithApple() {
  const rawNonce = Crypto.randomUUID();
  const hashedNonce = await Crypto.digestStringAsync(
    Crypto.CryptoDigestAlgorithm.SHA256,
    rawNonce
  );

  const apple = await AppleAuthentication.signInAsync({
    requestedScopes: [
      AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
      AppleAuthentication.AppleAuthenticationScope.EMAIL,
    ],
    nonce: hashedNonce,
  });

  if (!apple.identityToken) throw new Error("Missing Apple identity token");

  const provider = new OAuthProvider("apple.com");
  const credential = provider.credential({
    idToken: apple.identityToken,
    rawNonce,
  });
  return signInWithCredential(auth, credential);
}

iOS-only: hide Apple on Android (isAvailableAsync()). If you offer Google on iOS, plan Sign in with Apple before App Store review.

Guest / Anonymous Auth

Anonymous sign-in (signInAnonymously) creates a real UID you can later link to email, Google, or Apple—useful for "try first" funnels.

  • guestMode: true — show "Continue as guest".
  • autoGuestMode: false (keep this default) — do not auto-skip the auth stack; silent anonymous sessions complicate linking, analytics, and account deletion.

Upgrade guests with linkWithCredential. Link / unlink / delete often need a recent login—catch auth/requires-recent-login and prompt re-auth instead of showing a generic failure.

Protected Routes with Expo Router

Expo Router auth is a session gate, not separate middleware. Prefer:

Kit-shaped route groups (validate against your tree)

  • src/app/(auth)/ — login, signup, forgot-password
  • src/app/(tabs)/ — signed-in UI
  • Gate: AuthProvider (src/contexts/auth-context.tsx) + root Stack (src/app/_layout.tsx) / entry redirect

That is session-based redirect between auth stack and tabs—not custom middleware. Validate paths against your kit; do not invent deeper routes.

Illustrative root navigator (SDK 53+):

import { Stack } from "expo-router";
import { useAuth } from "@/contexts/auth-context"; // validate import path

function RootNavigator() {
  const { user, isLoading } = useAuth();

  // Keep splash visible until isLoading === false (see Expo auth docs)
  if (isLoading) return null;

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Protected guard={!!user}>
        <Stack.Screen name="(tabs)" />
      </Stack.Protected>
      <Stack.Protected guard={!user}>
        <Stack.Screen name="(auth)" />
      </Stack.Protected>
    </Stack>
  );
}

Loop prevention: wait for onAuthStateChanged / isLoading; one root gate (Stack.Protected); do not dual-replace between auth and tabs layouts; protected routes are client-side only, not server auth (Expo protected routes).

Errors That Burn Hours (Real Strings + Fixes)

1. auth/invalid-credential

What you see: Firebase: Error (auth/invalid-credential).

Why: With Email Enumeration Protection enabled (default on newer projects), signInWithEmailAndPassword returns this generic code for unknown email or wrong password instead of auth/user-not-found / auth/wrong-password (Firebase Auth JS reference; community confirmation aligns with Firebase's enumeration-protection behavior).

Fix:

  • Show a single message: "Email or password is incorrect."
  • Do not branch UX on user-not-found vs wrong-password unless you intentionally disable enumeration protection (usually a bad trade).
  • Also verify the Email/Password provider is enabled and you are hitting the correct Firebase project.

2. Expo Go limits for Google Sign-In / native auth (footgun)

What you see: missing native modules, Google crashes, or Apple tokens for the wrong bundle ID.

Why: Native Google Sign-In cannot run in Expo Go (Expo Google authentication). Kits using @react-native-firebase/*, google-signin, Apple auth, and expo-dev-client need expo run / EAS.

Fix: Ship a dev client; rebuild after plugins. Treat "works in Expo Go" as a footgun report, not success.

3. Android SHA-1 missing for Google

What you see: Google Sign-In fails on Android (often a developer / configuration style error), while iOS or another build type works.

Why: Android OAuth clients bind package name + SHA-1. Local debug, EAS credentials, upload key, and Play App Signing certificates can all differ (Expo Google guide; Firebase Android troubleshooting).

Fix:

  • Collect SHA-1 via eas credentials -p android and/or ./gradlew signingReport.
  • Add all relevant fingerprints in Firebase → Project settings → your Android app.
  • Re-download google-services.json and rebuild.
  • For Play Store installs, include the App signing key SHA-1, not only the upload key.

4. Apple Sign In rejects / capability missing

What you see: Apple sheet never appears, entitlement errors, auth/invalid-credential / malformed OAuth after Apple returns, or App Store rejection for missing Apple login.

Why: Missing ios.usesAppleSignIn / entitlement, wrong nonce handling (hashed to Apple, raw to Firebase), Firebase Apple provider misconfigured, or offering Google on iOS without Apple (Expo AppleAuthentication; Firebase Apple).

Fix:

  • Enable capability, rebuild the binary (JS-only updates will not add entitlements).
  • Fix nonce hashing.
  • Confirm Services ID / Team ID / Key in Firebase.
  • Keep Apple iOS-only in UI.
  • Ship Apple beside other third-party options before review.

5. Redirect / auth state loops with Expo Router

What you see: splash ↔ login ↔ tabs flicker; deep-link bounce; navigation thrash.

Why: Redirect before auth loaded, dual guards fighting, or a second session flag out of band from Firebase.

Fix: Gate on isLoading === false; one AuthProvider + Firebase listener; dual Stack.Protected (user / !user) or one redirect—not both; (auth) ↔ (tabs) via root _layout only—no invented middleware.

Extra footguns worth a checklist row

  • Missing files / env: GoogleService-Info.plist, google-services.json, EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID—all three must match the Firebase/Google project you think you are calling.
  • Sensitive account ops: link, unlink, and delete often need recent re-authentication; catch auth/requires-recent-login and prompt re-auth.
  • autoGuestMode: leaving this on can skip your auth stack entirely—keep the default false unless that is an explicit product decision.

When a Kit Helps (Soft CTA)

Want email/password, Google (googleSignIn), Apple iOS (appleSignIn), guest (guestMode), emailVerification default OFF, and (auth) / (tabs) gated by AuthProvider already structured? Soft CTA: Expo starter kit—Auth is wired; you still add credentials, config files, and a dev build. Not zero-setup billing.

After login, monetize with RevenueCat paywalls on Expo; keep auth and purchase identity consistent.

FAQ

Does Firebase Auth work in Expo Go?

No for this stack. Google Sign-In, Apple as you ship it, and @react-native-firebase/* need a development build. Expo Go is a footgun for auth QA.

Do I need email verification?

No. Keep emailVerification OFF until templates and continue URLs are ready.

Why does Google work locally but not on Play builds?

Different SHA-1 certificates. Add Play App signing (and upload) fingerprints in Firebase, refresh google-services.json, rebuild.

Is Stack.Protected required?

On SDK 53+ yes, prefer it. Older SDKs: layout redirects. Always wait for auth load, then gate (auth) vs (tabs).

Can guests keep data after signup?

Link the anonymous UID with linkWithCredential. Prompt re-auth when Firebase returns auth/requires-recent-login.

Conclusion

Shipping Firebase Auth on Expo means aligning Firebase providers, native Google/Apple credentials on a dev build, and an Expo Router session gate between (auth) and (tabs). Start with email/password, finish Google SHA-1 coverage, ship Apple on iOS with entitlement + nonce handling, keep guest and email-verification flags intentional, and use the error checklist for auth/invalid-credential, Expo Go limits, and redirect loops.

Prefer Auth already wired? Soft next step: the Expo starter kit—you still own credentials and the native build.

References

  1. Firebase Auth JS API — signInWithEmailAndPassword / AuthErrorCodes
  2. Authenticate Using Apple (Firebase)
  3. Expo — Using Google authentication
  4. Expo — AppleAuthentication
  5. Expo Router — Authentication
  6. Expo Router — Protected routes
  7. Firebase Android troubleshooting (SHA-1)
  8. CodeBaseHub Expo starter kit
  9. How to add RevenueCat paywalls to Expo

Related articles