Expo Quickstart
You will learn the following:
- Install
@clerk/expo
- Set your Clerk API keys
- Add
<ClerkProvider>
- Protect specific pages with authentication
- Use Clerk hooks to enable users to sign in and out
Install @clerk/clerk-expo
The Clerk Expo SDK gives you access to prebuilt components, hooks, and helpers to make user authentication easier.
Run the following command to install the SDK:
npm install @clerk/clerk-expo
yarn add @clerk/clerk-expo
pnpm add @clerk/clerk-expo
bun add @clerk/clerk-expo
Install @clerk/types
(optional)
Clerk's @clerk/types
package provides TypeScript type definitions.
Add the package to your project by running the following command:
npm install @clerk/types
yarn add @clerk/types
pnpm add @clerk/types
bun add @clerk/types
Add your Clerk Publishable Key to your .env
file. It can always be retrieved from the API keys page in the Clerk Dashboard.
- In the Clerk Dashboard, navigate to the API keys page.
- In the Quick Copy section, copy your Clerk Publishable Key.
- Paste your key into your
.env
file.
The final result should resemble the following:
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
Disable bot protection
Bot protection is enabled by default for all Clerk apps. However, it is not supported in native apps.
To turn off bot protection:
- In the Clerk Dashboard, navigate to the Attack protection page.
- Disable Bot sign-up protection.
Add <ClerkProvider>
to your root layout
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.
You must pass your Publishable Key as a prop to the <ClerkProvider>
component.
Clerk also provides <ClerkLoaded>
, which won't render its children until the Clerk API has loaded.
Add both components to your root layout as shown in the following example:
import { ClerkProvider, ClerkLoaded } from '@clerk/clerk-expo'
import { Slot } from 'expo-router'
export default function RootLayout() {
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!
if (!publishableKey) {
throw new Error('Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to your .env file')
}
return (
<ClerkProvider publishableKey={publishableKey}>
<ClerkLoaded>
<Slot />
</ClerkLoaded>
</ClerkProvider>
)
}
Configure the token cache
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.
To use expo-secure-store
as your token cache:
-
Run the following command to install the library:
terminal npm install expo-secure-store
terminal yarn add expo-secure-store
terminal pnpm add expo-secure-store
terminal bun add expo-secure-store
-
In your root directory, create a
cache.ts
file and add the following code:cache.ts import * as SecureStore from 'expo-secure-store' import { Platform } from 'react-native' import { TokenCache } from '@clerk/clerk-expo/dist/cache' const createTokenCache = (): TokenCache => { return { getToken: async (key: string) => { try { const item = await SecureStore.getItemAsync(key) if (item) { console.log(`${key} was used 🔐 \n`) } else { console.log('No values stored under key: ' + key) } return item } catch (error) { console.error('secure store get item error: ', error) await SecureStore.deleteItemAsync(key) return null } }, saveToken: (key: string, token: string) => { return SecureStore.setItemAsync(key, token) }, } } // SecureStore is not supported on the web export const tokenCache = Platform.OS !== 'web' ? createTokenCache() : undefined
-
Update your root layout to use the token cache:
app /_layout.tsx import { tokenCache } from '@/cache' import { ClerkProvider, ClerkLoaded } from '@clerk/clerk-expo' import { Slot } from 'expo-router' export default function RootLayout() { const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY! if (!publishableKey) { throw new Error('Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env') } return ( <ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}> <ClerkLoaded> <Slot /> </ClerkLoaded> </ClerkProvider> ) }
Add sign-up and sign-in pages
Clerk currently only supports control components for Expo native. UI components are only available for Expo web. Instead, you must build custom flows using Clerk's API. The following sections demonstrate how to build custom email/password sign-up and sign-in flows. If you want to use different authentication methods, such as passwordless or OAuth, see the dedicated custom flow guides.
Layout page
First, protect your sign-up and sign-in pages.
- Create an
(auth)
route group. This will group your sign-up and sign-in pages. - In the
(auth)
group, create a_layout.tsx
file. - Paste the following code. The
useAuth()
hook is used to access the user's authentication state. If the user is already signed in, they will be redirected to the home page.
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@clerk/clerk-expo'
export default function AuthRoutesLayout() {
const { isSignedIn } = useAuth()
if (isSignedIn) {
return <Redirect href={'/'} />
}
return <Stack />
}
Sign-up page
- In the
(auth)
group, create asign-up.tsx
file. - Paste the following code. The
useSignUp()
hook is used to create a sign-up flow. The user can sign up using their email and password and will receive an email verification code to confirm their email.
import * as React from 'react'
import { Text, TextInput, Button, View } from 'react-native'
import { useSignUp } from '@clerk/clerk-expo'
import { useRouter } from 'expo-router'
export default function SignUpScreen() {
const { isLoaded, signUp, setActive } = useSignUp()
const router = useRouter()
const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
const [pendingVerification, setPendingVerification] = React.useState(false)
const [code, setCode] = React.useState('')
// Handle submission of sign-up form
const onSignUpPress = async () => {
if (!isLoaded) return
// Start sign-up process using email and password provided
try {
await signUp.create({
emailAddress,
password,
})
// Send user an email with verification code
await signUp.prepareEmailAddressVerification({ strategy: 'email_code' })
// Set 'pendingVerification' to true to display second form
// and capture OTP code
setPendingVerification(true)
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
// Handle submission of verification form
const onVerifyPress = async () => {
if (!isLoaded) return
try {
// Use the code the user provided to attempt verification
const signUpAttempt = await signUp.attemptEmailAddressVerification({
code,
})
// If verification was completed, set the session to active
// and redirect the user
if (signUpAttempt.status === 'complete') {
await setActive({ session: signUpAttempt.createdSessionId })
router.replace('/')
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signUpAttempt, null, 2))
}
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
if (pendingVerification) {
return (
<>
<Text>Verify your email</Text>
<TextInput
value={code}
placeholder="Enter your verification code"
onChangeText={(code) => setCode(code)}
/>
<Button title="Verify" onPress={onVerifyPress} />
</>
)
}
return (
<View>
<>
<Text>Sign up</Text>
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Enter email"
onChangeText={(email) => setEmailAddress(email)}
/>
<TextInput
value={password}
placeholder="Enter password"
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
/>
<Button title="Continue" onPress={onSignUpPress} />
</>
</View>
)
}
Sign-in page
- In the
(auth)
group, create asign-in.tsx
file. - Paste the following code. The
useSignIn()
hook is used to create a sign-in flow. The user can sign in using email address and password, or navigate to the sign-up page.
import { useSignIn } from '@clerk/clerk-expo'
import { Link, useRouter } from 'expo-router'
import { Text, TextInput, Button, View } from 'react-native'
import React from 'react'
export default function Page() {
const { signIn, setActive, isLoaded } = useSignIn()
const router = useRouter()
const [emailAddress, setEmailAddress] = React.useState('')
const [password, setPassword] = React.useState('')
// Handle the submission of the sign-in form
const onSignInPress = async () => {
if (!isLoaded) return
// Start the sign-in process using the email and password provided
try {
const signInAttempt = await signIn.create({
identifier: emailAddress,
password,
})
// If sign-in process is complete, set the created session as active
// and redirect the user
if (signInAttempt.status === 'complete') {
await setActive({ session: signInAttempt.createdSessionId })
router.replace('/')
} else {
// If the status isn't complete, check why. User might need to
// complete further steps.
console.error(JSON.stringify(signInAttempt, null, 2))
}
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2))
}
}
return (
<View>
<TextInput
autoCapitalize="none"
value={emailAddress}
placeholder="Enter email"
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
/>
<TextInput
value={password}
placeholder="Enter password"
secureTextEntry={true}
onChangeText={(password) => setPassword(password)}
/>
<Button title="Sign in" onPress={onSignInPress} />
<View>
<Text>Don't have an account?</Text>
<Link href="/sign-up">
<Text>Sign up</Text>
</Link>
</View>
</View>
)
}
For more information about building these custom flows, including guided comments in the code examples, see the Build a custom email/password authentication flow guide.
Conditionally render content
You can control which content signed-in and signed-out users can see with Clerk's prebuilt control components. For this quickstart, you'll use:
<SignedIn>
: Children of this component can only be seen while signed in.<SignedOut>
: Children of this component can only be seen while signed out.
To get started:
- Create a
(home)
route group. - In the
(home)
group, create a_layout.tsx
file. - Paste the following code.
import { Stack } from 'expo-router/stack'
export default function Layout() {
return <Stack />
}
Then, in the same folder, create an index.tsx
file with the following code. It displays the user's email if they're signed in, or sign-in and sign-up links if they're not:
import { SignedIn, SignedOut, useUser } from '@clerk/clerk-expo'
import { Link } from 'expo-router'
import { Text, View } from 'react-native'
export default function Page() {
const { user } = useUser()
return (
<View>
<SignedIn>
<Text>Hello {user?.emailAddresses[0].emailAddress}</Text>
</SignedIn>
<SignedOut>
<Link href="/(auth)/sign-in">
<Text>Sign in</Text>
</Link>
<Link href="/(auth)/sign-up">
<Text>Sign up</Text>
</Link>
</SignedOut>
</View>
)
}
Create your first user
Run your project with the following command:
npm start
yarn start
pnpm start
bun start
Now visit your app's homepage at http://localhost:8081
. Sign up to create your first user.
Enable OTA updates
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.
Next steps
OAuth with Expo
Learn more how to build a custom OAuth flow with Expo.
MFA with Expo
Learn more how to build a custom multi-factor authentication flow with Expo.
Read session and user data
Learn how to read session and user data with Expo.
Sign-up and sign-in flow
Learn how to build a custom sign-up and sign-in authentication flow.
Feedback
Last updated on