# How to Use Clerk's AuthView in an Expo App

Mobile [authentication](https://clerk.com/docs/guides/how-clerk-works/overview.md) is one of the most complex parts of app development. Traditional approaches force developers to either build custom sign-in flows from scratch — often requiring hundreds of lines of code — or rely on WebView-based components that break the native user experience. Clerk's AuthView component changes this by rendering a fully native authentication UI with roughly five lines of code.

In this tutorial, you will build a complete Expo app with native sign-in and sign-up powered by AuthView. The finished app includes a public home screen, a native authentication screen, and a user profile page with Clerk's `UserButton` and `UserProfileView` components. AuthView is currently in beta — the core API is stable, but check the [Clerk changelog](https://clerk.com/changelog.md) for the latest status.

By the end of this guide, you will understand how AuthView works under the hood, how it synchronizes native sessions with the JavaScript SDK, and why native authentication outperforms WebView-based approaches in both security and user experience.

## What is AuthView?

AuthView is Clerk's native authentication component for Expo. Import it from `@clerk/expo/native` and it renders a complete sign-in and sign-up interface using SwiftUI on iOS and Jetpack Compose on Android. This is not a WebView wrapping a web page — it is a genuinely native UI built with each platform's own design framework.

AuthView automatically handles the full authentication lifecycle based on your Clerk Dashboard configuration. This includes email and password sign-in, email verification codes, [OAuth](https://clerk.com/docs/guides/configure/auth-strategies/social-connections/overview.md) providers like Google and Apple, [passkeys](https://clerk.com/docs/guides/configure/auth-strategies/sign-up-sign-in-options.md#passkeys), [multi-factor authentication (MFA)](https://clerk.com/docs/guides/configure/auth-strategies/sign-up-sign-in-options.md#multi-factor-authentication), and password recovery. When you enable a new authentication method in the Dashboard, AuthView picks it up automatically — no code changes or app updates needed.

AuthView was released in March 2026 with `@clerk/expo` 3.1 ([changelog](https://clerk.com/changelog/2026-03-09-expo-native-components.md)). It has intentionally minimal props:

- `mode` — accepts `"signIn"`, `"signUp"`, or `"signInOrUp"` (default). Determines which authentication flows are shown.
- `isDismissible` — a boolean (default `false`) that adds a dismiss button to the navigation bar.

This simplicity is by design. Authentication configuration belongs in the Clerk Dashboard, not scattered through your codebase. AuthView requires roughly five lines of code where a custom flow approach needs 25 or more lines per OAuth provider, plus manual state management, error handling, and token exchange logic.

## Why native authentication matters for mobile apps

Native authentication UIs provide meaningful advantages over WebView-based approaches in security, user experience, and conversion rates.

### Security

[RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252) — the IETF standard for OAuth 2.0 in native apps — explicitly states that native apps "MUST NOT use embedded user-agents" (Section 8.12) for authentication. Embedded WebViews expose credentials to the host app, enable phishing by hiding or spoofing the URL bar, and prevent users from verifying the identity of the authentication server.

Google enforces this standard: OAuth sign-in via embedded WebViews [is prohibited](https://developers.google.com/identity/protocols/oauth2/policies#webview), as [announced in 2016](https://developers.googleblog.com/en/modernizing-oauth-interactions-in-native-apps-for-better-usability-and-security/), requiring developers to use Chrome Custom Tabs (Android) or ASWebAuthenticationSession (iOS) instead. An Android WebView [AutoSpill vulnerability](https://www.darkreading.com/cyberattacks-data-breaches/android-vulnerability-leaks-credentials-from-password-managers-) demonstrated the risk by leaking credentials from the top 10 password managers through WebView autofill behavior.

AuthView avoids these risks entirely. It uses native platform APIs — ASAuthorization on iOS and Credential Manager on Android — for OAuth flows, matching the security model that platform vendors require. Firebase Auth and Supabase Auth both require developers to build their own login screens in React Native and handle OAuth through browser-based redirects. Neither offers pre-built native UI components for Expo.

### User experience and conversion

Authentication friction directly impacts conversion. Each additional authentication step reduces conversion by [10–15%](https://mojoauth.com/data-and-research-reports/passwordless-conversion-impact-report-2026/), and [46% of US consumers](https://www.corbado.com/blog/login-friction-kills-conversion) report failing to complete transactions due to authentication problems.

Native authentication UIs eliminate the context switch to a browser, render instantly without WebView startup time, and provide platform-consistent design that users trust. They also integrate with biometric authentication natively — [81% of smartphones](https://www.iproov.com/blog/biometric-statistics-70) had biometrics enabled as of 2022 (per Cisco Duo's Trusted Access Report), and Amazon reported [6x faster sign-in](https://www.aboutamazon.com/news/retail/amazon-passwordless-sign-in-passkey) after deploying passkeys to 175 million users.

## What you'll build

The finished app uses Expo Router's file-based routing with two route groups: one for authentication screens and one for protected content.

```text
src/app/
├── _layout.tsx          ← ClerkProvider setup
├── (auth)/
│   ├── _layout.tsx      ← Redirects signed-in users to home
│   └── sign-in.tsx      ← AuthView component
└── (home)/
    ├── _layout.tsx      ← Redirects signed-out users to sign-in
    ├── index.tsx         ← Home screen with conditional content
    └── profile.tsx       ← UserButton + UserProfileView
```

Each screen serves a distinct purpose:

- **`_layout.tsx` (root)** — wraps the entire app with `ClerkProvider` for authentication state management
- **`(auth)/sign-in.tsx`** — renders AuthView for native sign-in and sign-up
- **`(home)/index.tsx`** — shows different content based on authentication state using the `Show` component
- **`(home)/profile.tsx`** — displays the user's profile with `UserButton` (avatar with native modal) and `UserProfileView` (inline profile management)

## Prerequisites

### Tools and accounts needed

Before starting, confirm you have the following:

- **Node.js** — LTS version (20.x or later). Download from [nodejs.org](https://nodejs.org).
- **A Clerk account** — create one at [clerk.com](https://clerk.com/) and set up an application in the [Clerk Dashboard](https://dashboard.clerk.com).
- **Xcode** (for iOS) or **Android Studio** (for Android) — at least one is required to run a development build.
- **Basic familiarity with React and TypeScript** — you do not need to be an expert. This tutorial explains each code snippet line by line.

### Why a development build is required

AuthView uses native modules — SwiftUI on iOS and Jetpack Compose on Android — that are compiled into the app binary. These modules are **not** available in Expo Go, which only includes a fixed set of pre-bundled libraries.

A [development build](https://docs.expo.dev/develop/development-builds/introduction/) is a debug version of your app that includes `expo-dev-client` and any custom native modules your project needs. Create one by running `npx expo run:ios` or `npx expo run:android` instead of `npx expo start`.

> Expo Go cannot run AuthView or any other Clerk native component. You must use a development build for this entire tutorial. If you see "Native module not available" errors, you are likely running in Expo Go.

The development build is a one-time setup cost. Once built, JavaScript changes still hot-reload instantly — you only need to rebuild when adding or removing native dependencies.

**Checkpoint:** You should now have Node.js installed, a Clerk account created, and either Xcode or Android Studio set up.

## Setting up the Clerk application

### Create a Clerk application

1. Sign in to the [Clerk Dashboard](https://dashboard.clerk.com)
2. Select **Create application** (or use an existing one)
3. Choose the authentication methods you want to support — email, password, Google, Apple, or any combination
4. Copy your **Publishable Key** from the **API Keys** section — you will need this in a later step

### Enable the Native API

AuthView communicates with Clerk's backend through native SDK endpoints that must be explicitly enabled.

1. In the Clerk Dashboard, navigate to the **Native applications** page
2. Toggle **Native API** to enabled

This setting exposes the endpoints that AuthView needs for native sign-in, sign-up, and session management. Without it, native components will fail to authenticate.

### Configure social connections (optional)

If you want Google Sign-In or Apple Sign-In, configure them in the Clerk Dashboard under **User & Authentication > Social connections**. AuthView handles these flows automatically once they are enabled — you do not need to install additional packages or write custom hooks.

- **Google Sign-In** uses ASAuthorization on iOS and Credential Manager on Android (platform-native, not browser-based)
- **Apple Sign-In** uses the native Apple authentication framework

For detailed setup instructions, see the Clerk guides for [Sign in with Google](https://clerk.com/docs/expo/guides/configure/auth-strategies/sign-in-with-google.md) and [Sign in with Apple](https://clerk.com/docs/expo/guides/configure/auth-strategies/sign-in-with-apple.md).

> This tutorial works without any social connections configured. Email and password authentication is sufficient to follow along. You can add social providers later without changing any code.

**Checkpoint:** You should now have a Clerk application with authentication methods configured, Native API enabled, and your Publishable Key copied.

## Creating the Expo project

### Initialize a new Expo app

Create a new project using `create-expo-app` with the SDK 55 template:

```bash
npx create-expo-app@latest clerk-expo-authview --template default@sdk-55
```

The `--template default@sdk-55` flag pins the project to Expo SDK 55. Without it, `@latest` may pull a different SDK version during transition periods, causing the template structure to differ from this tutorial.

Navigate into the project directory:

```bash
cd clerk-expo-authview
```

> As of SDK 55, the default template uses a `src/app/` directory structure. All file paths in this tutorial are under `src/app/`. If you are using SDK 54 or earlier, routes are under `app/` instead — adjust paths accordingly. The older template also includes `app/(tabs)/`, `app/modal.tsx`, and `app/+not-found.tsx` instead of the files listed below.

### Remove default template files

The default template includes files that conflict with the routes in this tutorial. Remove the explore page:

```bash
rm src/app/explore.tsx
```

Uninstall `react-native-reanimated` and `react-native-worklets` to avoid Android build issues:

```bash
npx expo uninstall react-native-reanimated react-native-worklets
```

Open `src/app/_layout.tsx` and remove the `react-native-reanimated` import line if present. You will replace the full contents of this file in the ClerkProvider setup step.

### Install dependencies

Install the required packages:

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

Each package serves a specific purpose:

- **`@clerk/expo`** — Clerk's Expo SDK with native component support. Includes AuthView, UserButton, UserProfileView, and all authentication hooks.
- **`expo-secure-store`** — encrypted token storage using iOS Keychain and Android Keystore. Clerk uses this to persist session tokens securely across app restarts.
- **`expo-dev-client`** — enables development builds with custom native modules. Required because AuthView uses SwiftUI and Jetpack Compose code that Expo Go cannot run.

> Some Clerk tutorials also install `expo-auth-session` and `expo-web-browser`. These are optional peer dependencies only needed for browser-based OAuth flows using the `useSSO` or `useOAuth` hooks. AuthView handles OAuth entirely through native platform APIs, so these packages are **not** required for this tutorial.

### Set environment variables

Create a `.env` file in the project root:

```bash
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your-key-here
```

Replace `pk_test_your-key-here` with the Publishable Key from your Clerk Dashboard.

The `EXPO_PUBLIC_` prefix makes the variable accessible to client-side code. Metro (Expo's bundler) inlines [environment variables](https://clerk.com/docs/guides/development/clerk-environment-variables.md) with this prefix at build time.

> If Metro bundler was running before you installed the native modules, stop it now. After installing native dependencies, restart with `npx expo start -c` (the `-c` flag clears the bundler cache) or run `npx expo run:ios` / `npx expo run:android` for a fresh development build. This prevents "native module not available" errors from stale cache.

### Configure app.json plugins

Open `app.json` and add the required plugins to the `expo.plugins` array:

```json
{
  "expo": {
    "plugins": ["expo-router", "expo-secure-store", "@clerk/expo"]
  }
}
```

The `@clerk/expo` plugin integrates the native Clerk SDKs (`clerk-ios` and `clerk-android`) during the build process. It defaults to enabling the Apple Sign-In entitlement (`appleSignIn: true`). If you do not need Apple Sign-In, disable it explicitly:

```json
["@clerk/expo", { "appleSignIn": false }]
```

Run your first development build to verify everything compiles:

```bash
npx expo run:ios
```

Or for Android:

```bash
npx expo run:android
```

**Checkpoint:** You should now have a configured Expo project with all dependencies installed and a successful development build.

## Setting up ClerkProvider

Replace the contents of `src/app/_layout.tsx` with the following:

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

const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!

if (!publishableKey) {
  throw new Error('Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to your .env file')
}

export default function RootLayout() {
  return (
    <ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}>
      <Slot />
    </ClerkProvider>
  )
}
```

Here is what each part does:

- **`publishableKey`** — reads the `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` value from the `.env` file you created earlier. The `process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` reference works because Metro inlines `EXPO_PUBLIC_`-prefixed variables at build time. This is required in Core 3 — the `@clerk/expo` SDK does not auto-detect environment variables.
- **`tokenCache`** — imported from `@clerk/expo/token-cache`, this uses `expo-secure-store` under the hood. On iOS, tokens are stored in the Keychain (encrypted by Secure Enclave). On Android, tokens are stored in SharedPreferences encrypted with the Android Keystore. Sessions persist across app restarts.
- **`Slot`** — an Expo Router component that renders the current route's content. This is the standard pattern for layout files.

Session tokens have a 60-second lifetime and are [proactively refreshed](https://clerk.com/docs/guides/how-clerk-works/overview.md) every 50 seconds, so the user's authentication state stays current without any manual token management.

**Checkpoint:** ClerkProvider wraps the entire app. The app should still compile and run without errors.

## Building the authentication screen with AuthView

### Create the auth route group

Expo Router uses route groups — directories wrapped in parentheses like `(auth)` — to organize routes without affecting the URL structure. Create the auth layout at `src/app/(auth)/_layout.tsx`:

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

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

  if (!isLoaded) {
    return null
  }

  if (isSignedIn) {
    return <Redirect href="/(home)" />
  }

  return <Slot />
}
```

This layout checks the user's authentication state before rendering any auth screens:

- **`isLoaded`** — prevents premature redirects while Clerk's auth state initializes. Without this check, the layout might redirect before knowing whether the user is signed in.
- **`isSignedIn`** — if the user is already authenticated, the `<Redirect>` component navigates them to the home screen. This prevents signed-in users from seeing the sign-in screen.
- **`<Slot />`** — renders child routes (in this case, `sign-in.tsx`) when the user is not signed in.

> This tutorial uses the `<Redirect>` component from `expo-router` rather than imperative `router.replace()` inside render. `<Redirect>` integrates correctly with the navigation stack and avoids flash or back-stack issues. Clerk's minimal quickstart uses a simpler single-screen approach without route groups, but route groups scale better for production apps with multiple screens.

### Add the sign-in screen with AuthView

Create `src/app/(auth)/sign-in.tsx` — this is the core of the tutorial:

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

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

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

  return (
    <View style={styles.container}>
      <AuthView />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})
```

Here is a line-by-line breakdown:

- **`AuthView`** is imported from `@clerk/expo/native` — this is the native component entry point, separate from web components at `@clerk/expo/web`.
- **`useAuth({ treatPendingAsSignedOut: false })`** — this flag is critical. When a user authenticates through AuthView, the native SDK creates a session before the JavaScript SDK knows about it. There is a brief "pending" period during synchronization. With the default `treatPendingAsSignedOut: true`, `isSignedIn` flashes `false` during this sync, causing redirect loops. Setting it to `false` treats the pending state as signed-in, allowing the sync to complete.
- **`useEffect`** watches `isSignedIn` and redirects to the home screen when authentication completes.
- **`<AuthView />`** fills its parent container. The `flex: 1` style ensures it takes up the full screen.

The session synchronization flow works as follows:

1. The user interacts with the native AuthView UI
2. The native SDK (`clerk-ios` or `clerk-android`) creates a session
3. `@clerk/expo` syncs the native session to the JavaScript SDK
4. React hooks (`useAuth`, `useUser`) update automatically
5. The `useEffect` detects `isSignedIn === true` and triggers navigation

### Understanding AuthView props

AuthView has only two props — authentication methods are configured in the [Clerk Dashboard](https://dashboard.clerk.com), not in code.

The `mode` prop controls which flows are displayed:

```tsx
<AuthView />
```

This is equivalent to setting `mode="signInOrUp"`.

To restrict to sign-in only:

```tsx
<AuthView mode="signIn" />
```

To restrict to sign-up only:

```tsx
<AuthView mode="signUp" />
```

The `isDismissible` prop adds a close button to the navigation bar, allowing users to dismiss the authentication screen:

```tsx
<AuthView isDismissible={true} />
```

This is useful when authentication is optional rather than required.

**Checkpoint:** Run the app and you should see the native sign-in/sign-up interface. Create a test account to verify the redirect to the home screen works.

## Building the home screen

### Create the home route group

Create `src/app/(home)/_layout.tsx` to protect authenticated routes:

```tsx
import { useAuth } from '@clerk/expo'
import { Redirect, Slot } from 'expo-router'
import { View, Text, StyleSheet } from 'react-native'

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

  if (!isLoaded) {
    return (
      <View style={styles.loading}>
        <Text>Loading...</Text>
      </View>
    )
  }

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

  return <Slot />
}

const styles = StyleSheet.create({
  loading: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
})
```

This mirrors the auth layout. While the auth layout redirects _signed-in_ users away from authentication screens, the home layout redirects _signed-out_ users to the sign-in screen. The `isLoaded` check shows a loading indicator while Clerk determines the authentication state.

### Build the home screen

Create `src/app/(home)/index.tsx`:

```tsx
import { Show, useUser } from '@clerk/expo'
import { Link } from 'expo-router'
import { View, Text, StyleSheet } from 'react-native'

export default function HomeScreen() {
  const { user } = useUser()

  return (
    <View style={styles.container}>
      <Show when="signed-in">
        <Text style={styles.title}>
          Welcome, {user?.firstName || user?.emailAddresses[0]?.emailAddress}!
        </Text>
        <Text style={styles.subtitle}>You are signed in.</Text>
        <Link href="/(home)/profile" style={styles.link}>
          <Text style={styles.linkText}>View Profile</Text>
        </Link>
      </Show>

      <Show when="signed-out">
        <Text style={styles.title}>Welcome to Clerk + Expo</Text>
        <Text style={styles.subtitle}>Sign in to get started.</Text>
        <Link href="/(auth)/sign-in" style={styles.link}>
          <Text style={styles.linkText}>Sign In</Text>
        </Link>
      </Show>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 20,
  },
  link: {
    padding: 12,
  },
  linkText: {
    fontSize: 16,
    color: '#6C47FF',
    fontWeight: '600',
  },
})
```

The `Show` component is Clerk's Core 3 replacement for the older `SignedIn` and `SignedOut` components. It conditionally renders content based on authentication state:

- **`<Show when="signed-in">`** — content is visible only when the user is authenticated
- **`<Show when="signed-out">`** — content is visible only when the user is not authenticated

The `Show` component only controls _visibility_ — it is not a security boundary. Sensitive data must always be verified server-side. It supports a `fallback` prop for loading states and is preferred over manual `isSignedIn` conditional rendering for cleaner JSX.

The `useUser()` hook provides access to the current user's data, including `firstName`, `lastName`, `emailAddresses`, and `imageUrl`.

**Checkpoint:** The home screen should show different content based on authentication state. Signed-in users see a welcome message; signed-out users see a sign-in prompt.

## Adding the user profile page

Create `src/app/(home)/profile.tsx`. This screen demonstrates two more native components: `UserButton` and `UserProfileView`.

```tsx
import { useAuth, useUser } from '@clerk/expo'
import { UserButton, UserProfileView } from '@clerk/expo/native'
import { useRouter } from 'expo-router'
import { View, Text, Pressable, StyleSheet, ScrollView } from 'react-native'

export default function ProfileScreen() {
  const { user } = useUser()
  const { signOut } = useAuth()
  const router = useRouter()

  const handleSignOut = async () => {
    await signOut()
    router.replace('/(auth)/sign-in')
  }

  return (
    <ScrollView contentContainerStyle={styles.container}>
      <View style={styles.header}>
        <View style={styles.avatarContainer}>
          <UserButton />
        </View>
        <Text style={styles.name}>{user?.fullName}</Text>
        <Text style={styles.email}>{user?.emailAddresses[0]?.emailAddress}</Text>
      </View>

      <View style={styles.profileSection}>
        <Text style={styles.sectionTitle}>Profile Settings</Text>
        <UserProfileView />
      </View>

      <Pressable onPress={handleSignOut} style={styles.signOutButton}>
        <Text style={styles.signOutText}>Sign Out</Text>
      </Pressable>
    </ScrollView>
  )
}

const styles = StyleSheet.create({
  container: {
    flexGrow: 1,
    padding: 20,
  },
  header: {
    alignItems: 'center',
    marginBottom: 24,
    marginTop: 40,
  },
  avatarContainer: {
    width: 60,
    height: 60,
    marginBottom: 12,
  },
  name: {
    fontSize: 22,
    fontWeight: 'bold',
  },
  email: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  profileSection: {
    flex: 1,
    marginBottom: 24,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    marginBottom: 12,
  },
  signOutButton: {
    backgroundColor: '#FF3B30',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 40,
  },
  signOutText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
})
```

### The UserButton component

`UserButton` renders a circular avatar showing the user's profile image or initials. Tapping it opens a native profile management modal that includes account settings and sign-out functionality. The modal sign-out automatically syncs with the JavaScript SDK.

`UserButton` takes no props — it fills its parent container, so you control the size by styling the wrapping `View`. In this example, the `avatarContainer` style sets a 60×60 pixel area.

### The UserProfileView component

`UserProfileView` renders profile management UI directly in your screen. Clerk provides three approaches for displaying user profile management:

1. **Native modal via UserButton** — tapping `UserButton` opens a platform-native modal. This is the simplest approach and is what this tutorial uses for the avatar.
2. **Native modal via hook** — the `useUserProfileModal()` hook from `@clerk/expo` lets you programmatically present the profile modal from any component.
3. **Inline rendering** — embed `UserProfileView` directly in a screen, as shown in the profile page above.

This tutorial uses both: `UserButton` provides the native modal when tapped, and `UserProfileView` renders inline for a dedicated profile settings section.

> Do not combine `useUserProfileModal()` with a React Native `Modal` component. The native modal uses its own dismissal mechanism, and wrapping it in a React Native modal creates conflicting gesture handlers.

### Display user information and sign-out

The `useUser()` hook provides the current user's profile data:

- `user.fullName` — the user's full name
- `user.firstName` — the user's first name
- `user.emailAddresses[0].emailAddress` — the user's primary email
- `user.imageUrl` — the user's profile image URL

For sign-out, `UserButton` and `UserProfileView` both include built-in sign-out functionality that syncs with the JavaScript SDK automatically. The custom sign-out button in this example uses `useAuth().signOut()` for demonstration — it calls `signOut()` and then navigates back to the auth screen.

**Checkpoint:** The profile page should display a UserButton avatar, inline profile settings via UserProfileView, and user information. Tapping UserButton opens the native profile modal. The sign-out flow works correctly.

## Handling authentication state and navigation

### Session synchronization explained

When using native components, authentication happens in the native layer (SwiftUI/Jetpack Compose) before the JavaScript SDK is aware of it. Understanding this synchronization is important for avoiding common issues.

The synchronization flow:

1. The user authenticates through the native AuthView UI
2. The native SDK (`clerk-ios` or `clerk-android`) creates a session with Clerk's backend
3. `@clerk/expo` detects the native session and syncs it to the JavaScript SDK
4. React hooks (`useAuth`, `useUser`, `useSession`) update with the new state

Session tokens have a 60-second lifetime and are refreshed proactively at the 50-second mark. This means the JavaScript SDK always has a current token available for API calls. The `tokenCache` using `expo-secure-store` persists the session across app restarts — users do not need to re-authenticate unless the session is explicitly ended.

The `treatPendingAsSignedOut: false` option is critical when using native components. During the brief synchronization window between native authentication and JavaScript SDK state, auth status is "pending." Without this flag, `isSignedIn` defaults to `false` during the pending state, which triggers redirect logic prematurely. This flag is only needed in components where native authentication is actively happening — in route guard layouts like the home layout, the default behavior is correct.

### Protected route patterns

This tutorial uses a consistent pattern for protecting routes:

- **Auth layout** (`(auth)/_layout.tsx`) — redirects signed-in users away from auth screens using `<Redirect>`
- **Home layout** (`(home)/_layout.tsx`) — redirects signed-out users to the sign-in screen using `<Redirect>`
- Both layouts check `isLoaded` before making redirect decisions to avoid premature navigation
- The root layout contains only `ClerkProvider` and `<Slot />` — no auth logic

> Expo Router v5 (SDK 53+) introduced [`Stack.Protected`](https://docs.expo.dev/router/advanced/protected/) as a declarative alternative to `<Redirect>`. However, `Stack.Protected` requires a synchronous `guard` boolean, while native auth state goes through an asynchronous "pending" phase during native-to-JS session synchronization. Clerk's official native component examples use the `<Redirect>` pattern shown in this tutorial rather than `Stack.Protected`.

### Sign-out flow

There are two ways to handle sign-out:

1. **Built-in** — `UserButton` and `UserProfileView` include sign-out functionality that automatically syncs with the JavaScript SDK. No additional code is needed.
2. **Programmatic** — call `signOut()` from the `useAuth()` hook for a custom sign-out button:

```tsx
const { signOut } = useAuth()

const handleSignOut = async () => {
  await signOut()
}
```

After sign-out, the home layout's auth check detects that `isSignedIn` is `false` and automatically redirects to the sign-in screen. Including explicit navigation like `router.replace('/(auth)/sign-in')` provides an immediate visual transition but is not strictly required.

## Customization and configuration

### Configuring authentication methods

AuthView supports every authentication method that Clerk offers. Configure them in the [Clerk Dashboard](https://dashboard.clerk.com) — no code changes needed:

- Email and password
- Email verification codes
- Social providers (Google, Apple, GitHub, and more)
- Passkeys (requires additional setup — see the [Clerk passkeys guide](https://clerk.com/docs/reference/expo/passkeys.md))
- Multi-factor authentication

When you enable or disable a method in the Dashboard, AuthView reflects the change immediately in your app. This means you can add new authentication methods to a production app without shipping an app update.

### Handling OAuth providers with AuthView

Unlike the custom flow approach, AuthView handles Google and Apple Sign-In automatically. This is a significant developer experience advantage.

With custom flows, Google Sign-In on Android requires `expo-crypto` for nonce generation and the `useSignInWithGoogle` hook with manual session activation. Apple Sign-In requires `expo-apple-authentication`, `expo-crypto`, and the `useSignInWithApple` hook. Each provider adds roughly 25 or more lines of code.

AuthView eliminates all of this. Google Sign-In uses ASAuthorization on iOS and Credential Manager on Android — fully native, not browser-based. Apple Sign-In uses the native Apple authentication framework. All OAuth state management, token exchange, and error handling happens internally.

### Theming and appearance

AuthView renders using native platform styling:

- On iOS, it uses SwiftUI and follows iOS design conventions
- On Android, it uses Jetpack Compose and follows Material Design patterns
- Both platforms support system light and dark mode automatically

Native-level theming through `ClerkTheme` is available at the Swift and Kotlin layer for advanced customization ([iOS theming](https://clerk.com/docs/ios/guides/customizing-clerk/clerk-theme.md), [Android theming](https://clerk.com/docs/android/guides/customizing-clerk/clerk-theme.md)), but this is not currently exposed through React Native JavaScript props. For web components (`@clerk/expo/web`), the `appearance` prop and themes from `@clerk/ui` are available — these do not apply to native AuthView.

## Common issues and troubleshooting

### "Native module not available" errors

**Cause:** running the app in Expo Go instead of a development build.

**Fix:** use `npx expo run:ios` or `npx expo run:android` to create a development build. AuthView requires native modules (SwiftUI/Jetpack Compose) that Expo Go cannot provide.

### OAuth configuration errors

Common causes of OAuth failures:

- **Missing credentials** — Google or Apple Sign-In is not configured in the Clerk Dashboard
- **Incorrect Bundle ID or Team ID** — for Apple Sign-In, the Bundle ID and Team ID in the Clerk Dashboard must match your app's configuration
- **Missing SHA-1 fingerprint** — for Google Sign-In on Android, the SHA-1 certificate fingerprint must be registered
- **Missing iOS URL scheme** — the `@clerk/expo` config plugin usually handles this automatically, but verify your `app.json` includes the plugin

### Session not syncing after sign-in

**Cause:** missing `treatPendingAsSignedOut: false` on the `useAuth()` call in your auth screen.

**Symptoms:** redirect loops after successful native authentication, or the user appears signed out immediately after signing in.

**Fix:** pass `{ treatPendingAsSignedOut: false }` to `useAuth()` in any component that uses AuthView:

```tsx
const { isSignedIn } = useAuth({ treatPendingAsSignedOut: false })
```

This flag only needs to be set in components where native authentication is actively happening. In route guard layouts (like the home layout), the default `treatPendingAsSignedOut: true` is correct.

### Runtime error handling

AuthView handles all runtime errors automatically — this is a key advantage over building custom flows.

- **Field-level validation errors** (wrong password, invalid email, identifier not found) appear as inline error messages below the relevant input field
- **General errors** (network failures, server errors) display in a native modal sheet with a warning icon and description
- **Rate limiting** for verification codes is enforced with a visible 30-second cooldown timer
- **Haptic feedback** (iOS) triggers on field errors for tactile feedback

No error callbacks or error props are exposed to React Native. AuthView is intentionally opaque for error handling. If you need custom error handling (for logging or analytics), use custom sign-in flows with the `useSignIn` and `useSignUp` hooks instead.

### Development build caching issues

If you encounter unexpected behavior after installing or removing packages, clear the Metro bundler cache:

```bash
npx expo start --clear
```

For a complete rebuild, delete the native directories and rebuild:

```bash
rm -rf ios android && npx expo run:ios
```

## Comparing authentication approaches in Expo

Clerk offers three approaches for authentication in Expo apps. Each has different tradeoffs.

| Feature         |     AuthView (Native)     |       Custom Flows      |      Web Components     |
| --------------- | :-----------------------: | :---------------------: | :---------------------: |
| UI rendering    | SwiftUI / Jetpack Compose |       React Native      |         WebView         |
| Code required   |         \~5 lines         |  25+ lines per provider |        \~5 lines        |
| Expo Go support |             No            |           Yes           |         Web only        |
| OAuth handling  |  Automatic (native APIs)  | Manual hooks + packages |   Automatic (browser)   |
| Platform feel   |        Fully native       |      Custom styled      |         Web-like        |
| MFA support     |            Yes            |          Manual         |           Yes           |
| Passkey support |            Yes            |      Extra package      |           N/A           |
| Customization   |      Dashboard config     |       Full control      | Theme + appearance prop |
| Error handling  |         Automatic         |          Manual         |        Automatic        |
| Status          |            Beta           |          Stable         |          Stable         |

### Manual approach vs. streamlined approach

The following comparison shows the difference between a browser-based OAuth flow and the AuthView approach for the same result.

**Manual approach — browser-based OAuth with custom hooks:**

```tsx
import * as AuthSession from 'expo-auth-session'
import * as WebBrowser from 'expo-web-browser'
import { useSSO } from '@clerk/expo'
import { View, Pressable, Text } from 'react-native'

WebBrowser.maybeCompleteAuthSession()

export default function SignInScreen() {
  const { startSSOFlow } = useSSO()

  const handleGoogleSignIn = async () => {
    try {
      const { createdSessionId, setActive } = await startSSOFlow({
        strategy: 'oauth_google',
        redirectUrl: AuthSession.makeRedirectUri(),
      })
      if (createdSessionId) {
        await setActive!({ session: createdSessionId })
      }
    } catch (err) {
      console.error('OAuth error:', err)
    }
  }

  // Repeat for Apple, GitHub, and every other provider...
  return (
    <View>
      <Pressable onPress={handleGoogleSignIn}>
        <Text>Sign in with Google</Text>
      </Pressable>
    </View>
  )
}
```

This approach is functionally correct — `expo-web-browser` uses system browsers (ASWebAuthenticationSession on iOS, Chrome Custom Tabs on Android) per [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252) recommendations. However, it requires extra packages (`expo-auth-session`, `expo-web-browser`), manual error handling, and duplicated code for every OAuth provider.

**Streamlined approach — AuthView handles everything:**

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

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

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

  return (
    <View style={{ flex: 1 }}>
      <AuthView />
    </View>
  )
}
```

The same result — sign-in, sign-up, Google, Apple, MFA, passkeys, error handling — with no extra packages and no manual provider logic.

### When to use each approach

- **AuthView** — recommended for most apps. Minimal code, native UX, automatic support for all authentication methods. Use this unless you have a specific reason not to.
- **Custom flows** — when you need completely custom UI design, must support Expo Go, or need full control over every authentication step.
- **Web components** — for the Expo web platform or when web-based UI is acceptable for your mobile use case.

## Why Clerk for Expo authentication

### Developer experience advantages

AuthView eliminates hundreds of lines of custom authentication code. A single component handles sign-in, sign-up, OAuth, MFA, passkey, and password recovery flows. New authentication methods are added through Dashboard configuration — no code changes or app updates required.

The native SDK synchronization is handled transparently. Developers do not need to manage token exchange, session creation, or state synchronization between native and JavaScript layers. Secure token storage through `expo-secure-store` uses hardware-backed encryption (iOS Keychain and Android Keystore) without any configuration beyond including `tokenCache` in the `ClerkProvider`.

As of April 2026, Clerk is the only major authentication provider that ships official, first-party native UI components for Expo. Firebase Auth (`@react-native-firebase/auth`) provides API-only access with no pre-built UI — developers must build every login screen from scratch. Supabase Auth offers `@supabase/auth-ui-react` for web React, but has no React Native equivalent. Auth0 relies on browser-based OAuth through `react-native-auth0`. Clerk's `@clerk/expo` package includes AuthView, UserButton, and UserProfileView as native components, plus a config plugin that handles both native Google and Apple Sign-In from a single package.

### Production readiness

Clerk's internal controls are [designed to meet SOC 2 Type II criteria](https://trust.clerk.io/soc2/), with formal attestation pending. The platform supports HIPAA (with BAA), GDPR, and CCPA compliance. Session tokens use a 60-second lifetime with proactive refresh, minimizing the window for token misuse.

The [free tier](https://clerk.com/pricing) includes 50,000 monthly retained users per app, making it accessible for apps at any stage of development.

## Frequently asked questions

## FAQ

### Can I use AuthView with Expo Go?

No. AuthView requires native modules (SwiftUI on iOS and Jetpack Compose on Android) that are not available in Expo Go. You must use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) created with `npx expo run:ios` or `npx expo run:android`.

### What authentication methods does AuthView support?

AuthView supports all methods enabled in the [Clerk Dashboard](https://dashboard.clerk.com): email, phone, password, OAuth providers (Google, Apple, GitHub, and more), passkeys, and multi-factor authentication. When you enable a new method in the Dashboard, AuthView reflects it automatically.

### Do I need extra packages for Google or Apple Sign-In with AuthView?

No. AuthView handles OAuth providers automatically using native platform APIs (ASAuthorization on iOS, Credential Manager on Android). You do not need `expo-apple-authentication`, `expo-crypto`, or custom hooks.

### What Expo SDK version is required for AuthView?

Expo SDK 53 or later is required for Clerk native components. This tutorial targets SDK 55.

### How does AuthView differ from Clerk web components?

AuthView renders natively using SwiftUI (iOS) and Jetpack Compose (Android), providing a platform-native experience. Web components (`@clerk/expo/web`) render in a WebView. AuthView imports from `@clerk/expo/native`, while web components import from `@clerk/expo/web`.

### Can I customize the appearance of AuthView?

AuthView uses native platform styling and adapts to iOS and Android design patterns automatically, including system light and dark mode. Authentication methods are configured in the Clerk Dashboard. Native-level theming via `ClerkTheme` is available at the Swift/Kotlin layer for advanced customization but is not exposed through React Native props.

### How do I handle sign-out with native components?

`UserButton` and `UserProfileView` include built-in sign-out functionality that syncs with the JavaScript SDK automatically. For programmatic sign-out, use `useAuth().signOut()`.

### What happens if the user loses network connectivity during authentication?

AuthView handles network errors with built-in retry mechanisms and displays error messages in a native modal. For offline-first app requirements, Clerk offers experimental offline support via the `__experimental_resourceCache` property from `@clerk/expo/resource-cache`. See the [offline support guide](https://clerk.com/docs/guides/development/offline-support.md) for details.

### Can I use AuthView alongside custom authentication flows?

Yes. AuthView handles primary authentication while custom flows (using `useSignIn` and `useSignUp` hooks) can supplement specialized use cases. Session state is shared across both approaches.

### Is AuthView available for Android?

Yes. AuthView renders using Jetpack Compose on Android and SwiftUI on iOS, providing a fully native experience on both platforms.

### Does AuthView work with the React Native CLI (without Expo)?

AuthView requires the `@clerk/expo` package and its Expo config plugin. It works in any Expo project that supports development builds, including the Expo bare workflow (with `expo` installed and `expo prebuild`). Pure React Native CLI projects without Expo tooling are not supported for native components. For non-Expo setups, use `@clerk/clerk-react` with custom sign-in/sign-up flows.

### How do I test AuthView in my app?

Clerk testing tokens are designed for web testing frameworks (Playwright, Cypress) and do not integrate with native rendering. For E2E testing, use [Maestro](https://maestro.mobile.dev/) with a dedicated Clerk development instance and pre-created test accounts. For integration tests, mock Clerk hooks (`useAuth`, `useUser`, `useSession`) to test application logic without exercising the native UI.

### Does AuthView handle errors like wrong passwords or invalid emails automatically?

Yes. AuthView displays all runtime errors internally. Wrong passwords, invalid emails, and identifier-not-found errors appear as inline messages below the relevant field. Network and server errors display in a native modal. Verification codes are rate-limited with a visible 30-second cooldown. No error handling code is required from the developer.
