Use Clerk with Expo
Learn how to use Clerk to quickly and easily add secure authentication and user management to your Expo application.
Install @clerk/clerk-expo
Once you have an Expo application ready, you need to install Clerk's Expo SDK.
npm install @clerk/clerk-expo
yarn add @clerk/clerk-expo
pnpm 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.
module.exports = {
name: 'MyApp',
version: '1.0.0',
extra: {
clerkPublishableKey: process.env.CLERK_PUBLISHABLE_KEY,
},
};
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.
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",
},
});
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.
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.
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.
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>
);
}
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.
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",
},
});
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>
);
}
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 />
.
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",
},
});
OAuth sign-in
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.
import React from "react";
import * as WebBrowser from "expo-web-browser";
export const useWarmUpBrowser = () => {
React.useEffect(() => {
void WebBrowser.warmUpAsync();
return () => {
void WebBrowser.coolDownAsync();
};
}, []);
};
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;
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.
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",
},
});
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
.
You can use it as your client JWT storage by setting the tokenCache
prop in your <ClerkProvider>
as shown below.
Install expo-secure-store
npm install expo-secure-store
yarn add expo-secure-store
pnpm add install expo-secure-store
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",
}
});
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.
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",
}
});
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.
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>
);
}
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.
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>;
}
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
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:
Customization & Localization
Learn how to customize and localize the Clerk components.
Authentication Components
Learn more about all our authentication components.
Client Side Helpers
Learn more about our client side helpers and how to use them.
Feedback
Last updated on