There are three approaches for adding authentication to your Expo app.
Approach
Auth UI
Requires dev build
Best for
Hosted authentication
In-app browser
No (works in Expo Go)
Fastest hosted setup
Native components
Prebuilt native components
Yes
Prebuilt native UI
Custom flow
Your React Native components
No; native sign-in requires one
Full control over UI
Use the following tabs to choose your preferred approach:
Hosted authentication
Native components
Custom flow
Hosted authentication opens Account Portal in a browser authentication session. Account Portal supports the sign-in and sign-up methods enabled for your Clerk application.
In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.
Expo automatically adds the required config plugins to your app.json file when you install the packages. Verify that @clerk/expo and expo-secure-store appear in the plugins array:
Hosted authentication derives its default callback from these identifiers. On Android, the Clerk config plugin also registers the matching intent filter. Add the app on the Native applications page before you create a production build: the iOS bundle identifier, and the Android namespace and package name.
The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.
Add the component to your root layout and pass your Publishable Key and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:
Create a src/app/index.tsx file. The following example opens Account Portal for sign-up. After authentication, the SDK updates useAuth() with the signed-in state.
Enter your details and complete the authentication flow.
After signing up, your first user will be created and you'll be signed in.
Beta
This feature is currently in beta. Functionality may change before general availability. If you run into any issues, please reach out to our support team.
This approach uses Clerk's prebuilt native componentsExpo Icon that render using SwiftUI on iOS and Jetpack Compose on Android. Choose it when you want authentication rendered with native components and can use a development build.
In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.
Install the required packages. Use npx expo install to ensure SDK-compatible versions.
The Clerk Expo SDKExpo Icon gives you access to prebuilt components, hooks, and helpers to make user authentication easier.
Clerk stores the active user's session token in memory by default. In Expo apps, the recommended way to store sensitive data, such as tokens, is by using expo-secure-store which encrypts the data before storing it.
expo-dev-client allows you to build and run your app in development mode.
Expo automatically adds the required config plugins to your app.json file when you install the packages. Verify that @clerk/expo and expo-secure-store appear in the plugins array:
The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.
Add the component to your root layout and pass your Publishable Key and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:
Create a src/app/index.tsx file with the following code. If the user is signed in, it displays the <UserButton />. If they're not signed in, it displays a Sign up button that opens the <AuthView />.
Important
When using native components, pass { treatPendingAsSignedOut: false } to useAuth() so pending session tasks are not treated as signed out.
Important
Keep the React Native <Modal> that contains <AuthView /> mounted at the same level as your signed-in and signed-out content. Don't render the modal only inside signed-out content, because auth state can change before required session tasks are finished and unmount the modal too early.
This approach requires a development build because it uses native modules. It cannot run in Expo Go.
terminal
# Using Expo CLInpxexporun:iosnpxexporun:android# Using EAS Buildeasbuild--platformioseasbuild--platformandroid# Or using local prebuildnpxexpoprebuild&&npxexporun:ios--devicenpxexpoprebuild&&npxexporun:android--device
Then use the terminal shortcuts to run the app on your preferred platform:
Press i to open the iOS simulator.
Press a to open the Android emulator.
Scan the QR code with Expo Go to run the app on a physical device.
<AuthView /> automatically shows sign-in buttons for any social connections enabled in your Clerk Dashboard. However, native OAuth requires additional credential setup — without it, the buttons will appear but fail with an error when tapped.
You do not need to install expo-apple-authentication, expo-crypto, or use the useSignInWithApple() hook — <AuthView /> handles the sign-in flow automatically.
This approach uses Clerk's APIs with your own React Native components and works in Expo Go — no dev build required.
In the Clerk Dashboard, navigate to the Native applications page and enable the Native API. This is required to integrate Clerk in your native application or browser extension.
Install the required packages. Use npx expo install to ensure SDK-compatible versions.
The Clerk Expo SDKExpo Icon gives you access to prebuilt components, hooks, and helpers to make user authentication easier.
Clerk stores the active user's session token in memory by default. In Expo apps, the recommended way to store sensitive data, such as tokens, is by using expo-secure-store which encrypts the data before storing it.
The <ClerkProvider> component provides session and user context to Clerk's hooks and components. It's recommended to wrap your entire app at the entry point with <ClerkProvider> to make authentication globally accessible. See the reference docs for other configuration options.
Add the component to your root layout and pass your Publishable Key and tokenCache from @clerk/expo/token-cache as props, as shown in the following example:
Create a src/app/index.tsx file. The following example uses the useSignUp() hook to build a basic email and password sign-up form. Clerk emails the user a verification code, so the screen shows a code field once the sign-up starts.
src/app/index.tsx
import { useAuth, useSignUp } from'@clerk/expo'import { useState } from'react'import { Button, StyleSheet, Text, TextInput, View } from'react-native'exportdefaultfunctionMainScreen() {const { isLoaded,isSignedIn } =useAuth()const { signUp } =useSignUp()const [emailAddress,setEmailAddress] =useState('')const [password,setPassword] =useState('')const [code,setCode] =useState('')const [isVerifying,setIsVerifying] =useState(false)consthandleSignUp=async () => {const { error } =awaitsignUp.password({ emailAddress, password })if (error) {// Handle the error in your app.// See https://clerk.com/docs/guides/development/custom-flows/error-handlingreturn }const { error: sendError } =awaitsignUp.verifications.sendEmailCode()if (sendError) {// Handle the error in your app.return }setIsVerifying(true) }consthandleVerify=async () => {const { error } =awaitsignUp.verifications.verifyEmailCode({ code })if (error) {// Handle the error in your app.return }const { error: finalizeError } =awaitsignUp.finalize()if (finalizeError) {// Handle the error in your app. } }if (!isLoaded) {returnnull }if (isSignedIn) {return ( <Viewstyle={styles.container}> <Text>You're signed in</Text> </View> ) }if (isVerifying) {return ( <Viewstyle={styles.container}> <TextInputstyle={styles.input}value={code}placeholder="Enter your verification code"onChangeText={setCode}keyboardType="numeric" /> <Buttontitle="Verify"onPress={handleVerify} /> </View> ) }return ( <Viewstyle={styles.container}> <TextInputstyle={styles.input}autoCapitalize="none"value={emailAddress}placeholder="Enter email"onChangeText={setEmailAddress}keyboardType="email-address" /> <TextInputstyle={styles.input}value={password}placeholder="Enter password"secureTextEntry={true}onChangeText={setPassword} /> <Buttontitle="Sign up"onPress={handleSignUp} /> {/* Required for sign-up flows on Expo web. Clerk skips the browser CAPTCHA on iOS and Android */} <ViewnativeID="clerk-captcha" /> </View> )}conststyles=StyleSheet.create({ container: { flex:1, padding:20, gap:12, justifyContent:'center', }, input: { borderWidth:1, borderColor:'#ccc', borderRadius:8, padding:12, fontSize:16, },})
When verifyEmailCode() completes the sign-up, finalize() converts it into an active session and updates useAuth() with the signed-in state.
Enter your details and complete the authentication flow.
After signing up, your first user will be created and you'll be signed in.
For complete sign-up and sign-in flows with guided comments and error handling, see the Build a custom email/password authentication flow guide. To use other authentication methods, such as passwordless or OAuth, see the custom flow guides. To add native Sign in with Google or Sign in with Apple buttons, see the Sign in with Google and Sign in with Apple guides. These use native modules, so they require a development build and cannot run in Expo Go. The Expo SDK referenceExpo Icon lists the hooks and helpers available when building custom flows.
Though not required, it is recommended to implement over-the-air (OTA) updates in your Expo app. This enables you to easily roll out Clerk's feature updates and security patches as they're released without having to resubmit your app to mobile marketplaces.
See the expo-updates library to learn how to get started.