Once you have an Expo application ready, you need to install Clerk's Expo SDK.
terminal npm install @clerk/clerk-expo
terminal yarn add @clerk/clerk-expo
terminal pnpm add @clerk/clerk-expo
Below is an example of an app.config.js
file. To get your keys, go to the API Keys page on the Clerk dashboard .
app.config.js module . exports = {
name : 'MyApp' ,
version : '1.0.0' ,
extra : {
clerkPublishableKey : process . env . CLERK_PUBLISHABLE_KEY ,
} ,
};
Update your app entry file to include the <ClerkProvider>
wrapper. The <ClerkProvider>
component wraps your Expo application to provide active session and user context to Clerk's hooks and other components. It is recommended that the <ClerkProvider>
wraps everything else to enable the context to be accessible anywhere within the app.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider } from "@clerk/clerk-expo" ;
import Constants from "expo-constants"
export default function App () {
return (
< ClerkProvider publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}>
< SafeAreaView style = { styles .container}>
< Text >Hello world!</ Text >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
} ,
});
Clerk offers Control Components that allow you to protect your pages. In the example below, <SignedIn>
and <SignedOut>
control components are used.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut } from "@clerk/clerk-expo" ;
import Constants from "expo-constants"
export default function App () {
return (
< ClerkProvider publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}>
< SafeAreaView style = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
</ SignedIn >
< SignedOut >
< Text >You are Signed out</ Text >
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
} ,
});
Using expo requires you to create custom flows. Our quickstart guide shows you how to create a sign in and sign up flow using the useSignIn
and useSignUp
hooks.
Warning
The following flows are built on the default instance settings for Clerk. If you have changed your instance settings, you may need to make changes to the following code.
The examples below use email and password to sign a user up. You will notice that there is a conditonal flow in the sign up page for verifying a code called pendingVerification
. This is an important step, as it verifies that the user owns their email.
components /SignUpScreen.tsx import * as React from "react" ;
import { Text , TextInput , TouchableOpacity , View } from "react-native" ;
import { useSignUp } from "@clerk/clerk-expo" ;
export default function SignUpScreen () {
const { isLoaded , signUp , setActive } = useSignUp ();
const [ firstName , setFirstName ] = React .useState ( "" );
const [ lastName , setLastName ] = React .useState ( "" );
const [ emailAddress , setEmailAddress ] = React .useState ( "" );
const [ password , setPassword ] = React .useState ( "" );
const [ pendingVerification , setPendingVerification ] = React .useState ( false );
const [ code , setCode ] = React .useState ( "" );
// start the sign up process.
const onSignUpPress = async () => {
if ( ! isLoaded) {
return ;
}
try {
await signUp .create ({
firstName ,
lastName ,
emailAddress ,
password ,
});
// send the email.
await signUp .prepareEmailAddressVerification ({ strategy : "email_code" });
// change the UI to our pending section.
setPendingVerification ( true );
} catch (err : any ) {
console .error ( JSON .stringify (err , null , 2 ));
}
};
// This verifies the user using email code that is delivered.
const onPressVerify = async () => {
if ( ! isLoaded) {
return ;
}
try {
const completeSignUp = await signUp .attemptEmailAddressVerification ({
code ,
});
await setActive ({ session : completeSignUp .createdSessionId });
} catch (err : any ) {
console .error ( JSON .stringify (err , null , 2 ));
}
};
return (
< View >
{ ! pendingVerification && (
< View >
< View >
< TextInput
autoCapitalize = "none"
value = {firstName}
placeholder = "First Name..."
onChangeText = {(firstName) => setFirstName (firstName)}
/>
</ View >
< View >
< TextInput
autoCapitalize = "none"
value = {lastName}
placeholder = "Last Name..."
onChangeText = {(lastName) => setLastName (lastName)}
/>
</ View >
< View >
< TextInput
autoCapitalize = "none"
value = {emailAddress}
placeholder = "Email..."
onChangeText = {(email) => setEmailAddress (email)}
/>
</ View >
< View >
< TextInput
value = {password}
placeholder = "Password..."
placeholderTextColor = "#000"
secureTextEntry = { true }
onChangeText = {(password) => setPassword (password)}
/>
</ View >
< TouchableOpacity onPress = {onSignUpPress}>
< Text >Sign up</ Text >
</ TouchableOpacity >
</ View >
)}
{pendingVerification && (
< View >
< View >
< TextInput
value = {code}
placeholder = "Code..."
onChangeText = {(code) => setCode (code)}
/>
</ View >
< TouchableOpacity onPress = {onPressVerify}>
< Text >Verify Email</ Text >
</ TouchableOpacity >
</ View >
)}
</ View >
);
}
Note
Now would be good a time to test your sign up flow.
Make sure you update your app
file to use the component that you have created. To keep it simple, this example adds the <SignUpScreen />
to the <SignedOut>
component.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut } from "@clerk/clerk-expo" ;
import Constants from "expo-constants"
import SignUpScreen from "./components/SignUpScreen" ;
export default function App () {
return (
< ClerkProvider publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}>
< SafeAreaView styles = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
</ SignedIn >
< SignedOut >
< SignUpScreen />
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
} ,
});
components /SignInScreen.tsx import React from "react" ;
import { Text , TextInput , TouchableOpacity , View } from "react-native" ;
import { useSignIn } from "@clerk/clerk-expo" ;
export default function SignInScreen () {
const { signIn , setActive , isLoaded } = useSignIn ();
const [ emailAddress , setEmailAddress ] = React .useState ( "" );
const [ password , setPassword ] = React .useState ( "" );
const onSignInPress = async () => {
if ( ! isLoaded) {
return ;
}
try {
const completeSignIn = await signIn .create ({
identifier : emailAddress ,
password ,
});
// This is an important step,
// This indicates the user is signed in
await setActive ({ session : completeSignIn .createdSessionId });
} catch (err : any ) {
console .log (err);
}
};
return (
< View >
< View >
< TextInput
autoCapitalize = "none"
value = {emailAddress}
placeholder = "Email..."
onChangeText = {(emailAddress) => setEmailAddress (emailAddress)}
/>
</ View >
< View >
< TextInput
value = {password}
placeholder = "Password..."
secureTextEntry = { true }
onChangeText = {(password) => setPassword (password)}
/>
</ View >
< TouchableOpacity onPress = {onSignInPress}>
< Text >Sign in</ Text >
</ TouchableOpacity >
</ View >
);
}
Note
Now would be good a time to test your sign in flow. Restart your server to remove the session.
Make sure you update your app
file to use the component that you have created. In the <SignedOut>
component, replace the <SignUpScreen />
with the <SignInScreen />
.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut } from "@clerk/clerk-expo" ;
import Constants from "expo-constants"
import SignInScreen from "./components/SignInScreen" ;
export default function App () {
return (
< ClerkProvider publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}>
< SafeAreaView styles = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
</ SignedIn >
< SignedOut >
< SignInScreen />
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
} ,
});
If you want to add OAuth sign-in flows to your Expo application, Clerk's OAuth hook allows you to handle both sign in and sign up in a single flow.
hooks /useWarmUpBrowser.tsx import React from "react" ;
import * as WebBrowser from "expo-web-browser" ;
export const useWarmUpBrowser = () => {
React .useEffect (() => {
void WebBrowser .warmUpAsync ();
return () => {
void WebBrowser .coolDownAsync ();
};
} , []);
};
components /SignInWithOAuth.tsx import React from "react" ;
import * as WebBrowser from "expo-web-browser" ;
import { Button } from "react-native" ;
import { useOAuth } from "@clerk/clerk-expo" ;
import { useWarmUpBrowser } from "../hooks/useWarmUpBrowser" ;
WebBrowser .maybeCompleteAuthSession ();
const SignInWithOAuth = () => {
// Warm up the android browser to improve UX
// https://docs.expo.dev/guides/authentication/#improving-user-experience
useWarmUpBrowser ();
const { startOAuthFlow } = useOAuth ({ strategy : "oauth_google" });
const onPress = React .useCallback ( async () => {
try {
const { createdSessionId , signIn , signUp , setActive } =
await startOAuthFlow ();
if (createdSessionId) {
setActive ({ session : createdSessionId });
} else {
// Use signIn or signUp for next steps such as MFA
}
} catch (err) {
console .error ( "OAuth error" , err);
}
} , []);
return (
< Button
title = "Sign in with Google"
onPress = {onPress}
/>
);
}
export default SignInWithOAuth;
Note
Now would be a good time to test your OAuth flow. Restart your server to remove the session.
Make sure you update your app
file to use the component that you have created. Replace the <SignInScreen />
with the <SignInWithOAuth />
to the <SignedOut>
component.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut } from "@clerk/clerk-expo" ;
import Constants from "expo-constants"
import SignInWithOAuth from "./components/SignInWithOAuth" ;
export default function App () {
return (
< ClerkProvider publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}>
< SafeAreaView styles = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
</ SignedIn >
< SignedOut >
< SignInWithOAuth />
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
} ,
});
A token cache is important for persisting your JWT. By default, Clerk holds it in memory. However, this method shouldn't be used for Production applications.
Expo provides a way to encrypt and securely store key–value pairs locally on the device via expo-secure-store
.
You can use it as your client JWT storage by setting the tokenCache
prop in your <ClerkProvider>
as shown below.
terminal npm install expo-secure-store
terminal yarn add expo-secure-store
terminal pnpm add install expo-secure-store
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut } from "@clerk/clerk-expo" ;
import { SignInWithOAuth } from "./components/SignInWithOAuth" ;
import Constants from "expo-constants"
import * as SecureStore from "expo-secure-store" ;
const tokenCache = {
async getToken (key : string ) {
try {
return SecureStore .getItemAsync (key);
} catch (err) {
return null ;
}
} ,
async saveToken (key : string , value : string ) {
try {
return SecureStore .setItemAsync (key , value);
} catch (err) {
return ;
}
} ,
};
export default function App () {
return (
< ClerkProvider
tokenCache = {tokenCache}
publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}
>
< SafeAreaView styles = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
</ SignedIn >
< SignedOut >
< SignInWithOAuth />
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
}
});
To sign a user out, you can use the signOut
method from the useAuth
hook. This will remove the JWT from the token cache and remove the session.
app.tsx import React from "react" ;
import { SafeAreaView , Text , StyleSheet , View , Button } from "react-native" ;
import { ClerkProvider , SignedIn , SignedOut , useAuth } from "@clerk/clerk-expo" ;
import { SignInWithOAuth } from "./components/SignInWithOAuth" ;
import Constants from "expo-constants"
import * as SecureStore from "expo-secure-store" ;
const tokenCache = {
getToken (key : string ) {
try {
return SecureStore .getItemAsync (key);
} catch (err) {
return null ;
}
} ,
saveToken (key : string , value : string ) {
try {
return SecureStore .setItemAsync (key , value);
} catch (err) {
return null ;
}
} ,
};
const SignOut = () => {
const { isLoaded , signOut } = useAuth ();
if ( ! isLoaded) {
return null ;
}
return (
< View >
< Button
title = "Sign Out"
onPress = {() => {
signOut ();
}}
/>
</ View >
);
};
export default function App () {
return (
< ClerkProvider
tokenCache = {tokenCache}
publishableKey = { Constants . expoConfig . extra .clerkPublishableKey}
>
< SafeAreaView styles = { styles .container}>
< SignedIn >
< Text >You are Signed in</ Text >
< SignOut />
</ SignedIn >
< SignedOut >
< SignInWithOAuth />
</ SignedOut >
</ SafeAreaView >
</ ClerkProvider >
);
}
const styles = StyleSheet .create ({
container : {
flex : 1 ,
backgroundColor : "#fff" ,
alignItems : "center" ,
justifyContent : "center" ,
}
});
Clerk provides a set of hooks and helpers that you can use to access the active session and user data in your Expo application. Here are examples of how to use these helpers.
The useAuth
hook is a convenient way to access the current auth state. This hook provides the minimal information needed for data-loading and helper methods to manage the current active session.
components /UseAuthExample.tsx import { useAuth } from "@clerk/clerk-expo" ;
import { Text } from "react-native" ;
export default function UseAuthExample () {
const { isLoaded , userId , sessionId , getToken } = useAuth ();
// In case the user signs out while on the page.
if ( ! isLoaded || ! userId) {
return null ;
}
return (
< Text >
Hello, {userId} your current active session is {sessionId}
</ Text >
);
}
The useUser
hook is a convenient way to access the current user data where you need it. This hook provides the user data and helper methods to manage the current active session.
components /UseUserExample.tsx import { useUser } from "@clerk/clerk-expo" ;
import { Text } from "react-native" ;
export default function UseUserExample () {
const { isLoaded , isSignedIn , user } = useUser ();
if ( ! isLoaded || ! isSignedIn) {
return null ;
}
return < Text >Hello, { user .firstName} welcome to Clerk</ Text >;
}
Clerk recommends implementing over-the-air (OTA) updates into your Expo application. This enables easy roll out of feature updates and security patches as they're applied to Clerk's SDK's, without having to resubmit your application to the marketplace. Check out expo-updates
for how this can be accomplished.
Now that you have an application integrated with Clerk, you will want to read the following documentation: