Use Clerk with Expo
Learn how to use Clerk to quickly and easily add secure authentication and user management to your Expo application.
Currently the @clerk/expo
SDK only supports mobile app output. You can upvote the addition of Clerk supporting web output here(opens in a new tab).
Install @clerk/clerk-expo
Once you have an Expo application ready, you need to install Clerk's Expo SDK.
terminalnpm install @clerk/clerk-expo
terminalyarn add @clerk/clerk-expo
terminalpnpm add @clerk/clerk-expo
Set environment variables
Below is an example of an app.config.js
file. To get your keys, go to the API Keys page on the Clerk dashboard(opens in a new tab).
app.config.jsmodule.exports = { name: 'MyApp', version: '1.0.0', extra: { clerkPublishableKey: process.env.CLERK_PUBLISHABLE_KEY, }, };
For further information, check Environment variables in Expo(opens in a new tab)
Mount <ClerkProvider>
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.tsximport 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", }, });
Protecting your pages
Clerk offers Control Components that allow you to protect your pages. In the example below, <SignedIn>
and <SignedOut>
control components are used.
app.tsximport 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.
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.
Build your sign up
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.tsximport * 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> ); }
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.tsximport 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", }, });
Build your sign in
components/SignInScreen.tsximport 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> ); }
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.tsximport 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", }, });
OAuth login
If you want to add OAuth login flows to your Expo application, Clerk's OAuth hook allows you to handle both sign in and sign up in a single flow.
hooks/warmUpBrowser.tsximport React from "react"; import * as WebBrowser from "expo-web-browser"; export const useWarmUpBrowser = () => { React.useEffect(() => { void WebBrowser.warmUpAsync(); return () => { void WebBrowser.coolDownAsync(); }; }, []); };
components/SignInWithOAuth.tsximport 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;
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.tsximport 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", }, });
Adding a token cache
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
(opens in a new tab).
You can use it as your client JWT storage by setting the tokenCache
prop in your <ClerkProvider>
as shown below.
Install expo-secure-store
terminalnpm install expo-secure-store
terminalyarn add expo-secure-store
terminalpnpm add install expo-secure-store
app.tsximport 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", } });
Signing a user out
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.tsximport 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", } });
Read session & user data
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.
useAuth
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.tsximport { 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> ); }
useUser
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.tsximport { 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>; }
Additional consideration
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
(opens in a new tab) for how this can be accomplished.
Next steps
Now that you have an application integrated with Clerk, you will want to read the following documentation: