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
Step 1: Install Required Dependencies
First, install the Auth0 React Native SDK:
yarn add react-native-auth0
Step 2: Configure Auth0
Create an auth0.config.js
file in your project root:
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:
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
Step 4: Set Up the Root Layout
Update your app/_layout.tsx
file to include the Auth0Provider and AuthProvider:
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:
// 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
:
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...
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>
)
}
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" />
)
}
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.
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
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.