# Clerk vs Firebase Authentication for Expo

Clerk is the stronger choice for Expo apps, offering an Expo-first SDK with native SwiftUI and Jetpack Compose components, built-in [passkey](https://clerk.com/glossary.md#passkeys) support, and stable compatibility across Expo SDK upgrades. Firebase Auth integrates with Google's broader ecosystem but has experienced recurring compatibility issues with Expo's Metro bundler and requires workarounds for newer Expo SDK versions. Clerk also provides built-in organization management and [multi-tenancy](https://clerk.com/glossary.md#multi-tenancy) features that Firebase Auth lacks natively.

Why compare now? Expo SDK 53 shifted the playing field. Metro bundler's `package.json exports` support broke Firebase JS SDK integrations, triggering "Component auth has not been registered yet" errors and `initializeAuth()` crashes on Hermes ([Expo SDK 53 Changelog, 2025](https://expo.dev/changelog/sdk-53); [expo#36588](https://github.com/expo/expo/issues/36588); [firebase-js-sdk#9020](https://github.com/firebase/firebase-js-sdk/issues/9020)). Workarounds exist, but each subsequent Expo SDK release has introduced new Firebase friction. Clerk shipped [Core 3](https://clerk.com/changelog/2026-03-03-core-3.md) with native components, and the gap between these two has widened since early 2025.

## Quick comparison

| Dimension                                                                   |                          Clerk                          |                               Firebase Auth                              |
| :-------------------------------------------------------------------------- | :-----------------------------------------------------: | :----------------------------------------------------------------------: |
| Expo Go support (no dev build)                                              | Email/password, email codes, phone codes, browser OAuth |                           JS SDK only (limited)                          |
| Native prebuilt UI                                                          |                           Yes                           |                                    No                                    |
| [Passkeys](https://clerk.com/glossary.md#passkeys)                          |                           Yes                           |                                    No                                    |
| [Biometric](https://clerk.com/glossary.md#biometric-authentication) sign-in |                           Yes                           |                                    No                                    |
| Native Google Sign-In                                                       |                           Yes                           |                        Via RN Firebase (dev build)                       |
| Native Apple Sign-In                                                        |                           Yes                           |                        Via RN Firebase (dev build)                       |
| Organizations / RBAC                                                        |                           Yes                           |                                    No                                    |
| [MFA](https://clerk.com/glossary.md#multi-factor-authentication-mfa)        |                           Yes                           |                        Requires Identity Platform                        |
| Enterprise [SSO](https://clerk.com/glossary/single-sign-on-sso.md) (SAML)   |                           Yes                           |                        Requires Identity Platform                        |
| [Session management](https://clerk.com/glossary.md#session-management)      |              Automatic (expo-secure-store)              |            JS SDK: manual (AsyncStorage); RN Firebase: native            |
| SDK stability with Expo upgrades                                            |                           Yes                           | Breakages documented per SDK release (53, 54, 55); workarounds available |

## Understanding the two approaches

### Clerk: auth is the entire product

Clerk is a dedicated auth platform. The Expo SDK ([`@clerk/expo`](https://clerk.com/docs/reference/expo/overview.md)) is a first-party package maintained by Clerk's team. It uses a hybrid stateful + stateless architecture: short-lived 60-second [session tokens](https://clerk.com/glossary.md#customizable-session-tokens) auto-refresh in the background, stored in encrypted on-device storage via `expo-secure-store` ([How Clerk Works](https://clerk.com/docs/guides/how-clerk-works/overview.md)).

Because auth is the whole product, Clerk goes deeper on auth-specific features: [passkeys](https://clerk.com/docs/reference/expo/passkeys.md), native SwiftUI/Jetpack Compose components, built-in [organizations](https://clerk.com/docs/organizations/overview.md), and prebuilt UI that works out of the box. Expo's own authentication guide lists Clerk first, describing it as a "powerful, full-featured authentication service with excellent Expo support" ([Expo Authentication Guide, 2026](https://docs.expo.dev/develop/authentication/)).

### Firebase Auth: auth as part of a bigger ecosystem

Firebase Auth is one piece of Google's [BaaS](https://clerk.com/glossary.md#backend-as-a-service-baas) (Firestore, Cloud Functions, Storage, Analytics). For Expo, two SDK paths exist, which is itself a source of confusion:

**Firebase JS SDK** (`firebase` npm package): first-party Google, works in Expo Go, limited to web-compatible features. No Analytics or Crashlytics on mobile. Requires manual persistence setup via `getReactNativePersistence` + AsyncStorage. Expo SDK 53+ requires `firebase@^12.0.0`; earlier versions cause ES module resolution errors ([Expo Firebase guide](https://docs.expo.dev/guides/using-firebase/)).

**React Native Firebase** (`@react-native-firebase/*` by Invertase): community-maintained, requires a dev build, wraps native iOS/Android SDKs, supports all Firebase services. Auth persistence is handled natively.

### Expo SDK compatibility at a glance

| Expo SDK | Clerk (`@clerk/expo`) |                       Firebase JS SDK                      |                 React Native Firebase                 |
| -------: | :-------------------: | :--------------------------------------------------------: | :---------------------------------------------------: |
|      53+ |        Yes 3.0+       | firebase@^12.0.0 required; Metro exports workaround needed |             Works with config plugin fixes            |
|       54 |          Yes          |                       Same as SDK 53                       | forceStaticLinking + useFrameworks: "static" required |
|       55 |          Yes          |                       Same as SDK 53                       |   Same config as SDK 54; New Architecture mandatory   |

The architectural tradeoff is clear. Clerk goes deeper on auth. Firebase gives you a database, functions, and storage alongside auth, but auth gets less focused attention.

## How this comparison is structured

This article evaluates Clerk and Firebase across five dimensions. The table below shows what each measures and which provider the evidence favors, so you can weight them against your own priorities.

| Dimension            | What it measures                            |   Favors   | Key evidence                                                                  |
| :------------------- | :------------------------------------------ | :--------: | :---------------------------------------------------------------------------- |
| Developer experience | Setup time, Expo Go support, API ergonomics |    Clerk   | 2 packages vs SDK path decision; prebuilt UI vs build-from-scratch            |
| Feature depth        | Auth methods, orgs, passkeys, native UI     |    Clerk   | Passkeys, native components, organizations absent in Firebase                 |
| SDK stability        | Compatibility across Expo SDK upgrades      |    Clerk   | No documented breakages vs 3 consecutive SDK cycles with Firebase issues      |
| Migration / lock-in  | Data portability, migration tooling         | Comparable | Official migration path exists both directions; Clerk provides import tooling |

These dimensions aren't numerically scored. The evidence is presented for each throughout the article. Your team's priorities determine which dimensions matter most.

## Developer experience and setup

### Clerk setup

Two packages, one environment variable, and a provider wrapper. This works in Expo Go for basic flows ([Expo Using Clerk Guide](https://docs.expo.dev/guides/using-clerk/); [Hyperknot Auth Provider Comparison, 2024](https://blog.hyperknot.com/p/comparing-auth-providers)).

Install:

```bash
npx expo install @clerk/expo expo-secure-store
```

Add your publishable key to `.env`:

```bash
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
```

Wrap your root layout with `ClerkProvider`:

```tsx
import { ClerkProvider, ClerkLoaded } from '@clerk/expo'
import { tokenCache } from '@clerk/expo/token-cache'
import { Slot } from 'expo-router'

export default function RootLayout() {
  return (
    <ClerkProvider
      publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!}
      tokenCache={tokenCache}
    >
      <ClerkLoaded>
        <Slot />
      </ClerkLoaded>
    </ClerkProvider>
  )
}
```

Check auth state and conditionally render:

```tsx
import { useAuth } from '@clerk/expo'
import { Show } from '@clerk/expo/ui'

export default function Home() {
  const { signOut } = useAuth()

  return (
    <Show when="signed-in" fallback={<SignInScreen />}>
      <Text>You're signed in!</Text>
      <Button title="Sign out" onPress={() => signOut()} />
    </Show>
  )
}
```

Build a sign-in screen with the Core 3 hooks API:

```tsx
import { useSignIn } from '@clerk/expo'
import { useState } from 'react'
import { View, TextInput, Button, Text } from 'react-native'
import { useRouter, type Href } from 'expo-router'

export function SignInScreen() {
  const { signIn, errors, fetchStatus } = useSignIn()
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const router = useRouter()

  const onSignIn = async () => {
    const { error } = await signIn.password({ emailAddress: email, password })
    if (error) return

    if (signIn.status === 'complete') {
      await signIn.finalize({
        navigate: ({ session, decorateUrl }) => {
          if (session?.currentTask) return
          const url = decorateUrl('/')
          router.push(url as Href)
        },
      })
    } else if (signIn.status === 'needs_second_factor') {
      // Handle MFA: signIn.mfa.sendEmailCode(), etc.
      router.push('/(auth)/mfa')
    }
  }

  return (
    <View>
      <TextInput placeholder="Email" value={email} onChangeText={setEmail} />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      {errors?.fields?.password && <Text>{errors.fields.password.message}</Text>}
      <Button title="Sign in" onPress={onSignIn} disabled={fetchStatus === 'fetching'} />
    </View>
  )
}
```

That's it. Four files, working auth. The [Expo quickstart](https://clerk.com/docs/expo/getting-started/quickstart.md) has the full walkthrough.

### Firebase setup (abbreviated)

Firebase requires choosing an SDK path first, and the choice has cascading consequences.

**Option A: Firebase JS SDK** (works in Expo Go, limited features):

```typescript
import { initializeApp } from 'firebase/app'
import { initializeAuth, getReactNativePersistence } from 'firebase/auth'
import AsyncStorage from '@react-native-async-storage/async-storage'

const app = initializeApp(firebaseConfig)

// Without this, users get logged out every time the app closes
const auth = initializeAuth(app, {
  persistence: getReactNativePersistence(AsyncStorage),
})
```

> `getReactNativePersistence` had a long-standing TypeScript export bug ([Firebase JS SDK #7584](https://github.com/firebase/firebase-js-sdk/issues/7584), closed October 2023, fixed in later SDK releases). If you're on `firebase@^12.0.0` (required for Expo SDK 53+), this should be resolved. Older versions may still need `// @ts-ignore`.

**Option B: React Native Firebase** (dev build required, full native features):

Requires a development build with native modules, config plugin setup in `app.json`, and `google-services.json` / `GoogleService-Info.plist` configuration. No Expo Go support.

Then you build your own auth state listener:

```typescript
import auth from '@react-native-firebase/auth'
import { useEffect, useState } from 'react'

function useFirebaseAuth() {
  const [user, setUser] = useState(null)

  useEffect(() => {
    return auth().onAuthStateChanged(setUser)
  }, [])

  return user
}
```

After that, you build every auth screen from scratch: sign-in, sign-up, password reset, MFA enrollment, profile management. Firebase provides zero prebuilt UI components for React Native.

### Time to first auth

Clerk: install, wrap with provider, use hooks. Works in Expo Go.

Firebase: choose SDK path, configure native files or persistence, build custom UI from scratch. The JS SDK works in Expo Go for basic email/password, but native features (Google Sign-In, Apple Sign-In) require a dev build and React Native Firebase.

## Authentication methods

| Method                                                        |  Clerk  |                     Firebase Auth                    |
| :------------------------------------------------------------ | :-----: | :--------------------------------------------------: |
| Email/password                                                | Partial |                          Yes                         |
| Social [OAuth](https://clerk.com/glossary.md#oauth) (browser) |   Yes   |                          Yes                         |
| Native Google Sign-In                                         |   Yes   |             RN Firebase only (dev build)             |
| Native Apple Sign-In                                          |   Yes   |             RN Firebase only (dev build)             |
| Passkeys                                                      |   Yes   |                          No                          |
| Biometric (Face ID / Touch ID)                                |   Yes   |                          No                          |
| Phone / SMS                                                   |   Yes   |             reCAPTCHA fallback on JS SDK             |
| [Magic links](https://clerk.com/glossary.md#magic-links)      |   Yes   | Dynamic Links shut down Aug 2025; requires migration |
| Enterprise SSO (SAML/OIDC)                                    |   Yes   |          Requires Identity Platform upgrade          |
| MFA (TOTP)                                                    |   Yes   |                  Included in Tier 1                  |
| MFA (SMS)                                                     |   Yes   |                    Charged per SMS                   |
| Anonymous auth                                                |    No   |                          Yes                         |

### Passkeys

Clerk has native passkey support via [`@clerk/expo-passkeys`](https://clerk.com/docs/reference/expo/passkeys.md) since February 2025. Users create passkeys with `user.createPasskey()` and sign in with `signIn.passkey()`. Requires iOS 16+ or Android 9+ and a development build.

Firebase has no passkey support. The FIDO Alliance reports that over 15 billion online accounts can now sign in using passkeys as of late 2024 ([FIDO Alliance Passkey Index, Oct 2025](https://fidoalliance.org/wp-content/uploads/2025/10/FIDO-Passkey-Index-October-2025.pdf)), with a 93% login success rate compared to 63% for passwords. This is a significant gap for any auth provider to have.

### Native sign-in

Clerk's native sign-in hooks use platform APIs directly:

- **Native Apple Sign-In**: `useSignInWithApple()` wraps Apple's `ASAuthorization` framework. iOS only. Requires a dev build and `expo-apple-authentication` + `expo-crypto`. Released November 2025 ([Clerk Changelog, Nov 2025](https://clerk.com/changelog/2025-11-13-native-sign-in-with-apple-expo.md)).
- **Native Google Sign-In**: `useSignInWithGoogle()` uses `ASAuthorization` on iOS and Credential Manager on Android. Requires a dev build and `expo-crypto`. No additional Google sign-in packages needed (the `@clerk/expo` config plugin handles it). Released March 2026 ([Clerk Changelog, Mar 2026](https://clerk.com/changelog/2026-03-09-expo-native-components.md)).

Firebase's native sign-in requires React Native Firebase plus separate community packages: `@react-native-google-signin/google-signin` and `@invertase/react-native-apple-authentication`. Dev build required. The Firebase JS SDK only offers browser-based OAuth for Google and Apple ([Expo Firebase Guide](https://docs.expo.dev/guides/using-firebase/); [Expo Go vs Development Builds, 2024](https://expo.dev/blog/expo-go-vs-development-builds)).

### Identity Platform upgrade

Firebase's MFA (both TOTP and SMS) and enterprise SSO (SAML/OIDC) require upgrading from basic Firebase Auth to Google Cloud Identity Platform.

## Pre-built UI vs custom flows

### Clerk's three integration tiers

Tier 1 (JS-only) and Tier 2 (JS + native sign-in) are production-stable. Tier 3 (native components) is in beta as of March 2026 and should be evaluated before use in production.

**1. JavaScript-only** (works in Expo Go): Build custom UI with React Native components and Clerk hooks (`useAuth()`, `useUser()`, `useSignIn()`, `useOAuth()` for browser-based OAuth). Supports email/password, email codes, phone codes, and browser-based social login. Full control, no native build required.

**2. JS + Native Sign-In** (dev build required): Custom UI with native OAuth hooks. `useSignInWithApple()` (iOS only) and `useSignInWithGoogle()` (iOS + Android). Platform-native flows via ASAuthorization and Credential Manager. No browser redirect.

**3. Native Components (Beta)** (dev build required): Pre-built native UI rendered via SwiftUI on iOS and Jetpack Compose on Android. `<AuthView />` handles the entire sign-in/sign-up flow, including social providers, MFA, and password recovery, without needing individual hooks or their dependencies.

```tsx
import { AuthView } from '@clerk/expo/native'
import { useAuth } from '@clerk/expo'
import { useRouter } from 'expo-router'
import { useEffect } from 'react'

export default function SignInScreen() {
  const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false })
  const router = useRouter()

  useEffect(() => {
    if (isSignedIn) {
      router.replace('/(home)')
    }
  }, [isSignedIn])

  return <AuthView mode="signInOrUp" />
}
```

> Native components are in beta. They were released in March 2026 as part of `@clerk/expo` 3.1. See the [native components docs](https://clerk.com/docs/reference/expo/native-components/overview.md) for the latest status.

Clerk also provides `<UserButton />` (avatar that opens a native profile modal) and `<UserProfileView />` (complete profile management screen).

### Firebase: build everything yourself

Firebase provides zero prebuilt UI components for React Native. FirebaseUI exists for web, iOS, and Android natively, but not React Native ([Firebase Auth Documentation](https://firebase.google.com/docs/auth)). Every screen, sign-in, sign-up, password reset, MFA enrollment, profile management, must be built from scratch with `TextInput`, `TouchableOpacity`, and your own state management. Independent developer tutorials confirm this gap: building a complete auth flow with Clerk requires no custom screens, while Firebase requires building every screen from scratch ([DEV.to: Complete Login System with Expo and Clerk, 2025](https://dev.to/aaronksaunders/your-first-complete-login-system-in-react-native-with-expo-and-clerk-3696)).

The tradeoff is real: pre-built components add bundle size. `@clerk/expo` and its dependencies are heavier than Firebase's auth-only SDK. But they also save weeks of development time. The question is whether your team would rather spend that time building auth screens or shipping product features.

## Session management and token handling

### Clerk

[Session tokens](https://clerk.com/docs/guides/how-clerk-works/overview.md) are 60-second JWTs that auto-refresh every 50 seconds (per [Clerk's architecture documentation](https://clerk.com/docs/guides/how-clerk-works/overview.md); these are internal implementation details not independently audited). The short lifetime minimizes the window for token misuse. Tokens are cached in `expo-secure-store`, which uses the iOS Keychain and Android Keystore for encrypted on-device storage ([expo-secure-store docs](https://docs.expo.dev/versions/latest/sdk/securestore/)).

Session lifetimes are configurable from 5 minutes to 10 years. Core 3 introduced proactive background token refresh, so your app never waits for a mid-request refresh.

For offline scenarios, Clerk offers experimental support (not recommended as a sole production dependency for offline-first apps) via `resourceCache` from `@clerk/expo/resource-cache`. This bootstraps the app using cached resources and returns cached tokens. When the network is truly unavailable, `getToken()` throws a `ClerkOfflineError` (in Core 3). Previously it returned `null`, which was ambiguous with the signed-out state.

### Firebase

Token behavior depends on which SDK path you chose.

**Firebase JS SDK on React Native**: defaults to memory-only persistence. Users get logged out every time the app closes unless you configure `getReactNativePersistence` with AsyncStorage (see the setup section above).

**React Native Firebase**: handles persistence natively via iOS Keychain and Android Keystore. No manual setup needed.

**Token lifecycle (both SDKs)**: Firebase ID tokens expire after 1 hour. Both SDKs automatically refresh them using stored refresh tokens. The refresh happens in the background without developer intervention. Refresh tokens don't expire unless the user changes their password, the account is disabled, or tokens are explicitly revoked via the Admin SDK.

> On Expo SDK 53+, there's a documented Catch-22 with the Firebase JS SDK: the Metro config workarounds needed for auth persistence can conflict with Firestore transactions ([Expo GitHub Issue #36588](https://github.com/expo/expo/issues/36588)). This doesn't affect React Native Firebase.

### Offline behavior

Clerk's offline support is experimental and not recommended as a production dependency for offline-first apps. The resource cache bootstraps using cached resources and `getToken()` returns cached tokens when available.

Firebase's React Native Firebase SDK handles offline auth state natively and is production-stable. The Firebase JS SDK's Firestore offline persistence doesn't work on React Native (no IndexedDB), but auth state persists if you configured AsyncStorage.

## User management and organizations

### User objects compared

Clerk's user object is rich: `firstName`, `lastName`, `username`, multiple emails and phone numbers, metadata (public/private/unsafe), organization memberships, roles, active sessions, banned/locked status, and `imageUrl` with auto-generated avatars.

Firebase's user object has 5 core fields: `uid`, `email`, `displayName`, `photoURL`, `emailVerified`, plus `phoneNumber`, `isAnonymous`, and `metadata`. [Custom claims](https://clerk.com/glossary.md#claims) are limited to 1,000 bytes. There's no built-in user search.

### Organizations

Clerk has first-class [organizations](https://clerk.com/docs/organizations/overview.md). Users belong to multiple orgs. Each org gets built-in [roles and permissions](https://clerk.com/docs/organizations/roles-permissions.md), member invitations, verified domain auto-enrollment, and per-org enterprise SSO connections. On mobile Expo, you manage organizations using Clerk's hooks and Backend API. On Expo Web, prebuilt components like `<OrganizationSwitcher />` and `<OrganizationProfile />` are also available.

Firebase has no organization concept. [Multi-tenancy](https://clerk.com/glossary/multi-tenancy.md) via Identity Platform uses isolated tenant silos where a user can't belong to multiple tenants. No member management, no invitations, no org switching. Everything must be custom-built.

If you're building a team workspace, project management tool, or any multi-tenant SaaS, Clerk's org support saves months of custom development.

## Expo Router integration

Both Clerk and Firebase work with Expo Router's protected route patterns. The structural patterns are similar; the difference is how auth state is provided.

### Clerk + Expo Router

```tsx
import { useAuth } from '@clerk/expo'
import { Redirect, Stack } from 'expo-router'

export default function HomeLayout() {
  const { isSignedIn, isLoaded } = useAuth()

  if (!isLoaded) return null

  if (!isSignedIn) {
    return <Redirect href="/(auth)/sign-in" />
  }

  return <Stack />
}
```

### Firebase + Expo Router

```tsx
import { useFirebaseAuth } from '@/hooks/useFirebaseAuth'
import { Stack, Redirect } from 'expo-router'

export default function AppLayout() {
  const user = useFirebaseAuth()

  if (user === undefined) return null // loading

  if (!user) return <Redirect href="/sign-in" />

  return <Stack />
}
```

The patterns look similar. The difference is upstream: Clerk's `useAuth()` is backed by a managed provider with automatic token refresh. Firebase's auth state comes from `onAuthStateChanged`, which you wrap yourself.

## Stability across Expo SDK upgrades

### Firebase's compatibility history

Three consecutive Expo SDK releases (53, 54, 55) each introduced Firebase compatibility issues that required configuration changes or workarounds. Here is the incident history and current resolution state for each (all issue statuses verified March 2026):

**Expo SDK 53** (April 2025): Metro's `package.json exports` broke the Firebase JS SDK. Multiple GitHub issues documented the fallout: [expo#36588](https://github.com/expo/expo/issues/36588) (open, workaround documented), [expo#36602](https://github.com/expo/expo/issues/36602) (closed, redirected), [expo#36496](https://github.com/expo/expo/issues/36496) (closed, resolved). `initializeAuth()` crashed on Hermes with "INTERNAL ASSERTION FAILED: Expected a class definition" ([firebase-js-sdk#9020](https://github.com/firebase/firebase-js-sdk/issues/9020), closed as stale). React Native Firebase's auth plugin broke with Swift AppDelegates ([react-native-firebase#8487](https://github.com/invertase/react-native-firebase/issues/8487), closed, RNFB added SDK 53 support). The primary workaround: set `unstable_enablePackageExports = false` in Metro config. An Expo maintainer commented: "we don't maintain firebase or any integration with it." SDK 53+ also requires `firebase@^12.0.0` ([Expo Firebase guide](https://docs.expo.dev/guides/using-firebase/)).

**Expo SDK 54** (September 2025): Non-modular header errors on iOS ([expo#39607](https://github.com/expo/expo/issues/39607), closed September 2025; workaround: `buildReactNativeFromSource: true`). Wrong `packageImportPath` on Android ([react-native-firebase#8761](https://github.com/invertase/react-native-firebase/issues/8761), closed as stale). Config plugin publishing regression in v23.8.0 ([react-native-firebase#8829](https://github.com/invertase/react-native-firebase/issues/8829), fixed in v23.8.4+).

**Expo SDK 55** (February 2026): Dropped Legacy Architecture entirely (`newArchEnabled` config option removed). Initial reports of iOS build errors with React Native Firebase ([react-native-firebase#8908](https://github.com/invertase/react-native-firebase/issues/8908), closed March 2026 with solution provided). The fix is configuration-only: `expo-build-properties` with `forceStaticLinking` and `useFrameworks: "static"`. No code changes to React Native Firebase were required; existing v23.8.8 works.

All known issues from SDK 53-55 have documented resolutions or workarounds (statuses verified March 2026). The recurring cost is configuration debugging during upgrades rather than blocked functionality, but it is a maintenance consideration that compounds with each Expo SDK release.

### Clerk's compatibility history

`@clerk/expo` 3.0 requires Expo SDK 53+ and was built for it. No documented breakages with Expo SDK upgrades to date (as of March 2026). That said, `@clerk/expo` is a newer SDK with fewer major version transitions behind it, so the track record is shorter.

Native components use the TurboModule spec and are New Architecture native. For upgrading between Clerk SDK versions, `npx @clerk/upgrade` provides an automated codemod.

> React Native Firebase is maintained by Invertase (a community organization), not Google. Clerk's Expo SDK is maintained by Clerk's own engineering team.

## Using Clerk auth with a Firebase backend

A common question: can I use Clerk for auth and Firebase for the database?

### The official integration (deprecated)

Clerk previously offered a built-in Firebase integration via `getToken({ template: 'integration_firebase' })`. This is now deprecated. New applications can't enable it. Existing apps continue to work, but once disabled, it can't be re-enabled.

### The DIY pattern for new apps

The architecture looks like this:

1. User authenticates with Clerk (using `@clerk/expo`)
2. Your server endpoint verifies the Clerk session [JWT](https://clerk.com/glossary.md#json-web-token)
3. Server uses Firebase Admin SDK to mint a Firebase custom token
4. Client calls `signInWithCustomToken()` from Firebase JS SDK
5. Firestore queries work with `request.auth` in security rules

Here's abbreviated server-side pseudocode:

```typescript
import { clerkClient } from '@clerk/express'
import admin from 'firebase-admin'

app.post('/firebase-token', async (req, res) => {
  // Verify the Clerk session
  const { userId } = req.auth

  // Mint a Firebase custom token using the Clerk user ID
  const firebaseToken = await admin.auth().createCustomToken(userId)

  res.json({ token: firebaseToken })
})
```

The client then exchanges this token:

```typescript
import { signInWithCustomToken } from 'firebase/auth'

const response = await fetch('/firebase-token', {
  /* auth headers */
})
const { token } = await response.json()
await signInWithCustomToken(auth, token)
```

> Auth states are independent. Signing out of Clerk doesn't sign out of Firebase. You must sync sign-out manually in your app.

### Gotchas

- Firebase custom tokens are a one-time exchange credential that expires after 1 hour. After `signInWithCustomToken()`, the user gets a normal Firebase session with auto-refreshing ID tokens. The custom token itself isn't the session.
- Firestore offline persistence doesn't work on React Native (no IndexedDB).
- Additional latency from the server round-trip for token exchange.

## Migration: Firebase to Clerk

Clerk provides an [official migration path](https://clerk.com/docs/guides/development/migrating/firebase.md) for Firebase users.

### The process

Export your Firebase users via CLI:

```bash
firebase auth:export firebase-users.json --format=json --project <your-project-id>
```

Retrieve password hash parameters from the Firebase Console (`base64_signer_key`, `base64_salt_separator`, `rounds`, `mem_cost`).

Run a migration script that POSTs to Clerk's Backend API with `password_hasher: 'scrypt_firebase'`. Each user is created with `external_id` set to the Firebase `localId` to preserve your existing data relationships. OAuth-only users can be imported with `skip_password_requirement: true`.

### What to watch for

- **Rate limits**: Clerk's import API returns 429 responses under heavy load. Your script should handle backoff.
- **Password hashing**: Firebase uses modified scrypt. Clerk handles the conversion automatically during import.
- **Active sessions**: migrating auth in a mobile app is tricky because you can't force-update installed versions. Plan for a graceful transition period where both old and new auth work.

## Migration: Clerk to Firebase

Migration works in the other direction too. Clerk doesn't lock in user data.

Export users via Clerk's Backend API (`GET /v1/users` with pagination). Import into Firebase using the Admin SDK's `importUsers()` method. Clerk uses bcrypt password hashing by default, and Firebase's `importUsers()` supports bcrypt via `hash.algorithm: 'BCRYPT'`.

OAuth-only users will need to re-link their social providers on first sign-in after migration. No official Clerk-to-Firebase migration guide exists, so this requires custom scripting.

This is a less common direction, but knowing it's possible matters when evaluating lock-in.

## When to choose which

| Your situation                            |  Recommended  | Why                                                                                                              |
| :---------------------------------------- | :-----------: | :--------------------------------------------------------------------------------------------------------------- |
| New Expo project, auth is primary concern |     Clerk     | Expo-first SDK, prebuilt components, passkeys, orgs                                                              |
| Already using Firestore + Cloud Functions | Evaluate both | Firebase Auth integrates natively; Clerk requires DIY token bridge                                               |
| B2B SaaS with team workspaces             |     Clerk     | Built-in organizations, [RBAC](https://clerk.com/glossary.md#role-based-access-control-rbac), member invitations |
| Need passkeys or biometric auth           |     Clerk     | Firebase has no passkey support                                                                                  |
| Frustrated with Firebase + Expo breakages |     Clerk     | Stable SDK maintained by Clerk's team                                                                            |
| Prototyping in Expo Go                    |     Clerk     | Email/password and browser OAuth work without dev build                                                          |
| Enterprise SSO (SAML/OIDC) requirement    |     Clerk     | Built-in on Business+; Firebase requires Identity Platform upgrade                                               |

For most Expo developers starting a new project, Clerk's developer experience, Expo-first SDK, and built-in features make it the stronger choice. Firebase Auth still fits if you're deep in the Google ecosystem and primarily need basic email and social auth at scale.

Ready to try it? The [Expo quickstart](https://clerk.com/docs/expo/getting-started/quickstart.md) gets you to working auth in under 5 minutes. Or explore Clerk's [Expo authentication](https://clerk.com/expo-authentication) landing page for a broader overview.

## Frequently asked questions

## FAQ

### Does Clerk work with Expo Go?

Yes, for email/password, email codes, phone codes, and browser-based OAuth using the JS-only approach. Native components, passkeys, native Google/Apple sign-in, biometric auth, and useSSO() all require a development build.

### Can I use Clerk for auth and Firebase for the database?

Yes, but the official Clerk Firebase integration is deprecated. New apps need a DIY approach: a server endpoint verifies the Clerk JWT, mints a Firebase custom token via the Admin SDK, and the client calls signInWithCustomToken(). The custom token is a one-time exchange. After sign-in, the user gets a normal Firebase session with auto-refreshing ID tokens. Auth states must be synced manually (signing out of Clerk doesn't sign out of Firebase).

### How do passkeys work in Clerk's Expo SDK?

Install @clerk/expo-passkeys. Users create passkeys via user.createPasskey() and sign in via signIn.passkey(). Requires iOS 16+ or Android 9+ and a development build.

### What happens to auth when the user is offline?

Clerk offers experimental offline support (not yet recommended as a production dependency) via resourceCache that bootstraps from cached resources and returns cached tokens. In Core 3, getToken() throws ClerkOfflineError when offline (previously it returned null, which was ambiguous with the signed-out state). Firebase's React Native Firebase SDK handles offline auth state natively. The Firebase JS SDK defaults to memory-only persistence on React Native unless configured with AsyncStorage.

### Can I migrate from Firebase to Clerk without logging out all users?

Clerk provides an official migration process that imports Firebase users with their password hashes (Firebase uses modified scrypt; Clerk handles the conversion). Users can sign in with existing credentials after migration. For mobile apps, plan a graceful transition period since you can't force-update installed versions.

### Does Clerk support native Google and Apple Sign-In?

Yes. Native Apple Sign-In (iOS only) since November 2025 via useSignInWithApple(). Native Google Sign-In (iOS + Android) since March 2026 via useSignInWithGoogle(). Both use platform-native APIs (ASAuthorization on iOS, Credential Manager on Android) with no browser redirect. Both require a development build.

### What is Firebase's compatibility history with recent Expo SDK upgrades?

Expo SDK 53, 54, and 55 each introduced Firebase compatibility issues. React Native Firebase is maintained by Invertase (community), not Google, and Expo SDK upgrades often change Metro bundler behavior or React Native internals. Firebase's two-SDK architecture (JS SDK vs native) means both paths can be affected independently. As of March 2026, all known issues have documented resolutions or workarounds: Metro exports workaround for SDK 53, forceStaticLinking config for SDK 54/55, and RNFB v23.8.4+ for the publishing regression.

### Is Firebase Auth actually free?

The Spark (free) plan caps at 3,000 daily active users. New sign-ins are rejected when exceeded. The Blaze plan gives 50K MAU free, then charges per-MAU with tiered pricing. Phone/SMS auth costs $0.01-$0.48 per message. TOTP MFA is included in Tier 1 at no extra cost. SAML/OIDC requires an Identity Platform upgrade to Tier 2 pricing ($0.015/MAU with only 50 free).
