Implementing a robust authentication system is crucial for mobile applications. In this guide, I'll walk you through setting up Auth0 authentication with Expo Router, creating a seamless and secure user experience. Prerequisites Before starting, ensure you have: An Expo project using Expo Router An Auth0 account with a configured application Basic understanding of React Native and TypeScript An Expo project using Expo Router An Auth0 account with a configured application Basic understanding of React Native and TypeScript Step 1: Install Required Dependencies First, install the Auth0 React Native SDK: yarn add react-native-auth0 yarn add react-native-auth0 Step 2: Configure Auth0 Create an auth0.config.js file in your project root: auth0.config.js const config = { clientId: "YOUR_AUTH0_CLIENT_ID", domain: "YOUR_AUTH0_DOMAIN", } export default config const config = { clientId: "YOUR_AUTH0_CLIENT_ID", domain: "YOUR_AUTH0_DOMAIN", } export default config Replace the placeholders with your actual Auth0 credentials. Step 3: Create an Authentication Context The authentication context will manage the auth state throughout your app. Create a file called useAuth.tsx in your hooks directory: useAuth.tsx import { createContext, useContext, useEffect, useState } from "react" import { useAuth0 } from "react-native-auth0" import { router, useSegments, useRootNavigationState } from "expo-router" // Define the shape of our auth context type AuthContextType = { signIn: () => Promise<void> signOut: () => Promise<void> isAuthenticated: boolean isLoading: boolean user: any error: Error | null } // Create the context with a default value const AuthContext = createContext<AuthContextType | null>(null) // Provider component that wraps the app export function AuthProvider({ children }: { children: React.ReactNode }) { const { authorize, clearSession, user, error, getCredentials, isLoading } = useAuth0() const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false) const segments = useSegments() const navigationState = useRootNavigationState() // Check if the user is authenticated and redirect accordingly useEffect(() => { if (!navigationState?.key) return const inAuthGroup = segments[0] === "(auth)" if (isAuthenticated && inAuthGroup) { // Redirect authenticated users from auth screens to the main app router.replace("/(tabs)") } else if (!isAuthenticated && !inAuthGroup) { // Redirect unauthenticated users to the login screen router.replace("/(auth)/login") } }, [isAuthenticated, segments, navigationState?.key]) // Update authentication state when user changes useEffect(() => { setIsAuthenticated(!!user) }, [user]) // Sign in function const signIn = async () => { try { await authorize() const credentials = await getCredentials() console.log("Auth credentials:", credentials) setIsAuthenticated(true) } catch (e) { console.error("Login error:", e) } } // Sign out function const signOut = async () => { try { await clearSession() setIsAuthenticated(false) } catch (e) { console.error("Logout error:", e) } } return ( <AuthContext.Provider value={{ signIn, signOut, isAuthenticated, isLoading, user, error, }} > {children} </AuthContext.Provider> ) } // Custom hook to use the auth context export function useAuth() { const context = useContext(AuthContext) if (!context) { throw new Error("useAuth must be used within an AuthProvider") } return context } import { createContext, useContext, useEffect, useState } from "react" import { useAuth0 } from "react-native-auth0" import { router, useSegments, useRootNavigationState } from "expo-router" // Define the shape of our auth context type AuthContextType = { signIn: () => Promise<void> signOut: () => Promise<void> isAuthenticated: boolean isLoading: boolean user: any error: Error | null } // Create the context with a default value const AuthContext = createContext<AuthContextType | null>(null) // Provider component that wraps the app export function AuthProvider({ children }: { children: React.ReactNode }) { const { authorize, clearSession, user, error, getCredentials, isLoading } = useAuth0() const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false) const segments = useSegments() const navigationState = useRootNavigationState() // Check if the user is authenticated and redirect accordingly useEffect(() => { if (!navigationState?.key) return const inAuthGroup = segments[0] === "(auth)" if (isAuthenticated && inAuthGroup) { // Redirect authenticated users from auth screens to the main app router.replace("/(tabs)") } else if (!isAuthenticated && !inAuthGroup) { // Redirect unauthenticated users to the login screen router.replace("/(auth)/login") } }, [isAuthenticated, segments, navigationState?.key]) // Update authentication state when user changes useEffect(() => { setIsAuthenticated(!!user) }, [user]) // Sign in function const signIn = async () => { try { await authorize() const credentials = await getCredentials() console.log("Auth credentials:", credentials) setIsAuthenticated(true) } catch (e) { console.error("Login error:", e) } } // Sign out function const signOut = async () => { try { await clearSession() setIsAuthenticated(false) } catch (e) { console.error("Logout error:", e) } } return ( <AuthContext.Provider value={{ signIn, signOut, isAuthenticated, isLoading, user, error, }} > {children} </AuthContext.Provider> ) } // Custom hook to use the auth context export function useAuth() { const context = useContext(AuthContext) if (!context) { throw new Error("useAuth must be used within an AuthProvider") } return context } This context provides: Authentication state management Sign-in and sign-out functions Automatic redirection based on authentication status Access to user information and error states Authentication state management Sign-in and sign-out functions Automatic redirection based on authentication status Access to user information and error states Step 4: Set Up the Root Layout Update your app/_layout.tsx file to include the Auth0Provider and AuthProvider: app/_layout.tsx import { Auth0Provider } from "react-native-auth0" import config from "@/auth0.config" import { AuthProvider } from "@/hooks/useAuth" // Other imports... export default function RootLayout() { // Other code... return ( <Auth0Provider domain={config.domain} clientId={config.clientId}> <AuthProvider> <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}> <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(auth)" options={{ headerShown: false }} /> <Stack.Screen name="+not-found" /> </Stack> <StatusBar style={colorScheme === "dark" ? "light" : "dark"} /> </ThemeProvider> </AuthProvider> </Auth0Provider> ) } import { Auth0Provider } from "react-native-auth0" import config from "@/auth0.config" import { AuthProvider } from "@/hooks/useAuth" // Other imports... export default function RootLayout() { // Other code... return ( <Auth0Provider domain={config.domain} clientId={config.clientId}> <AuthProvider> <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}> <Stack> <Stack.Screen name="(tabs)" options={{ headerShown: false }} /> <Stack.Screen name="(auth)" options={{ headerShown: false }} /> <Stack.Screen name="+not-found" /> </Stack> <StatusBar style={colorScheme === "dark" ? "light" : "dark"} /> </ThemeProvider> </AuthProvider> </Auth0Provider> ) } Step 5: Create the Authentication Group Expo Router uses directory-based routing. Create an (auth) directory in your app folder with a layout file: (auth) // app/(auth)/_layout.tsx import { Stack } from "expo-router" export default function AuthLayout() { return ( <Stack screenOptions={{ headerShown: false }}> <Stack.Screen name="login" /> </Stack> ) } // app/(auth)/_layout.tsx import { Stack } from "expo-router" export default function AuthLayout() { return ( <Stack screenOptions={{ headerShown: false }}> <Stack.Screen name="login" /> </Stack> ) } Step 6: Create the Login Screen Create a login screen in app/(auth)/login.tsx: app/(auth)/login.tsx import { ThemedText } from "@/components/ThemedText" import { useAuth } from "@/hooks/useAuth" import { StyleSheet, View, TouchableOpacity, ActivityIndicator, } from "react-native" export default function LoginScreen() { const { signIn, isLoading, error } = useAuth() return ( <View style={styles.container}> <View style={styles.content}> <ThemedText style={styles.title}>Welcome to Your App</ThemedText> <ThemedText style={styles.subtitle}>Sign in to continue</ThemedText> <TouchableOpacity style={styles.button} onPress={signIn} disabled={isLoading} > {isLoading ? ( <ActivityIndicator color="#fff" /> ) : ( <ThemedText style={styles.buttonText}>Sign In</ThemedText> )} </TouchableOpacity> {error && ( <ThemedText style={styles.errorText}>{error.message}</ThemedText> )} </View> </View> ) } // Styles... import { ThemedText } from "@/components/ThemedText" import { useAuth } from "@/hooks/useAuth" import { StyleSheet, View, TouchableOpacity, ActivityIndicator, } from "react-native" export default function LoginScreen() { const { signIn, isLoading, error } = useAuth() return ( <View style={styles.container}> <View style={styles.content}> <ThemedText style={styles.title}>Welcome to Your App</ThemedText> <ThemedText style={styles.subtitle}>Sign in to continue</ThemedText> <TouchableOpacity style={styles.button} onPress={signIn} disabled={isLoading} > {isLoading ? ( <ActivityIndicator color="#fff" /> ) : ( <ThemedText style={styles.buttonText}>Sign In</ThemedText> )} </TouchableOpacity> {error && ( <ThemedText style={styles.errorText}>{error.message}</ThemedText> )} </View> </View> ) } // Styles... Step 7: Create a Profile Screen Add a profile screen to display user information and provide a logout option: // app/(tabs)/profile.tsx import { ThemedText } from "@/components/ThemedText" import { useAuth } from "@/hooks/useAuth" import { StyleSheet, View, TouchableOpacity, Image, ScrollView, } from "react-native" export default function ProfileScreen() { const { user, signOut, isLoading } = useAuth() return ( <ScrollView style={styles.container}> <View style={styles.header}> {user?.picture ? ( <Image source={{ uri: user.picture }} style={styles.avatar} /> ) : ( <View style={styles.avatarPlaceholder}> <ThemedText style={styles.avatarText}> {user?.name?.charAt(0) || user?.email?.charAt(0) || "?"} </ThemedText> </View> )} <ThemedText style={styles.name}>{user?.name || "User"}</ThemedText> <ThemedText style={styles.email}>{user?.email || ""}</ThemedText> </View> {/* User information display */} <View style={styles.actions}> <TouchableOpacity style={styles.logoutButton} onPress={signOut} disabled={isLoading} > <ThemedText style={styles.logoutText}>Sign Out</ThemedText> </TouchableOpacity> </View> </ScrollView> ) } // Styles... // app/(tabs)/profile.tsx import { ThemedText } from "@/components/ThemedText" import { useAuth } from "@/hooks/useAuth" import { StyleSheet, View, TouchableOpacity, Image, ScrollView, } from "react-native" export default function ProfileScreen() { const { user, signOut, isLoading } = useAuth() return ( <ScrollView style={styles.container}> <View style={styles.header}> {user?.picture ? ( <Image source={{ uri: user.picture }} style={styles.avatar} /> ) : ( <View style={styles.avatarPlaceholder}> <ThemedText style={styles.avatarText}> {user?.name?.charAt(0) || user?.email?.charAt(0) || "?"} </ThemedText> </View> )} <ThemedText style={styles.name}>{user?.name || "User"}</ThemedText> <ThemedText style={styles.email}>{user?.email || ""}</ThemedText> </View> {/* User information display */} <View style={styles.actions}> <TouchableOpacity style={styles.logoutButton} onPress={signOut} disabled={isLoading} > <ThemedText style={styles.logoutText}>Sign Out</ThemedText> </TouchableOpacity> </View> </ScrollView> ) } // Styles... Step 8: Update the Tabs Layout Ensure your tabs layout includes the profile tab and checks authentication: // app/(tabs)/_layout.tsx import { useAuth } from "@/hooks/useAuth" // Other imports... export default function TabLayout() { const { isAuthenticated } = useAuth() // Redirect to login if not authenticated React.useEffect(() => { if (!isAuthenticated) { // The AuthProvider will handle the redirect } }, [isAuthenticated]) return ( <Tabs screenOptions={{ // Tab options... }} > {/* Other tabs */} <Tabs.Screen name="profile" options={{ title: "Profile", tabBarIcon: ({ color }) => ( <IconSymbol size={28} name="person.fill" color={color} /> ), }} /> </Tabs> ) } // app/(tabs)/_layout.tsx import { useAuth } from "@/hooks/useAuth" // Other imports... export default function TabLayout() { const { isAuthenticated } = useAuth() // Redirect to login if not authenticated React.useEffect(() => { if (!isAuthenticated) { // The AuthProvider will handle the redirect } }, [isAuthenticated]) return ( <Tabs screenOptions={{ // Tab options... }} > {/* Other tabs */} <Tabs.Screen name="profile" options={{ title: "Profile", tabBarIcon: ({ color }) => ( <IconSymbol size={28} name="person.fill" color={color} /> ), }} /> </Tabs> ) } Step 9: Create a Root Redirect Finally, create a root index file to handle initial routing: // app/index.tsx import { Redirect } from "expo-router" import { useAuth } from "@/hooks/useAuth" export default function Index() { const { isAuthenticated, isLoading } = useAuth() // While checking authentication status, don't redirect yet if (isLoading) { return null } // Redirect based on authentication status return isAuthenticated ? ( <Redirect href="/(tabs)" /> ) : ( <Redirect href="/(auth)/login" /> ) } // app/index.tsx import { Redirect } from "expo-router" import { useAuth } from "@/hooks/useAuth" export default function Index() { const { isAuthenticated, isLoading } = useAuth() // While checking authentication status, don't redirect yet if (isLoading) { return null } // Redirect based on authentication status return isAuthenticated ? ( <Redirect href="/(tabs)" /> ) : ( <Redirect href="/(auth)/login" /> ) } How It Works Initial Load: When the app starts, it checks the authentication status. Authentication Flow: Unauthenticated users are directed to the login screen After successful login, users are redirected to the main app The profile screen displays user information and provides logout functionality Protected Routes: The AuthProvider automatically protects routes by redirecting unauthenticated users to the login screen. Initial Load: When the app starts, it checks the authentication status. Initial Load Authentication Flow: Unauthenticated users are directed to the login screen After successful login, users are redirected to the main app The profile screen displays user information and provides logout functionality Authentication Flow Unauthenticated users are directed to the login screen After successful login, users are redirected to the main app The profile screen displays user information and provides logout functionality Unauthenticated users are directed to the login screen After successful login, users are redirected to the main app The profile screen displays user information and provides logout functionality Protected Routes: The AuthProvider automatically protects routes by redirecting unauthenticated users to the login screen. Protected Routes Benefits of This Approach Clean Separation: Authentication logic is isolated in a dedicated context Route Protection: Automatic redirection based on authentication status Reusable Authentication: The useAuth hook can be used throughout the app Seamless UX: Users are directed to the appropriate screens based on their authentication status Clean Separation: Authentication logic is isolated in a dedicated context Clean Separation Route Protection: Automatic redirection based on authentication status Route Protection Reusable Authentication: The useAuth hook can be used throughout the app Reusable Authentication Seamless UX: Users are directed to the appropriate screens based on their authentication status Seamless UX Conclusion Setting up Auth0 with Expo Router provides a robust authentication system for your mobile application. This approach leverages Expo Router's group-based routing to create a clean separation between authenticated and unauthenticated content, while the authentication context manages the state and provides a consistent interface for authentication operations. By following this guide, you've implemented a complete authentication flow that handles login, logout, and protected routes in a maintainable and scalable way.