Adding authentication to an Expo React Native app comes down to three decisions: which provider to use, how to store the session securely, and how to gate your screens. For most apps the fastest reliable path is a managed provider (Supabase, Clerk, or Firebase) plus Expo Router for route protection — you avoid building password hashing and token handling yourself.
Pick an auth approach
You have four realistic options:
- Supabase Auth — email/password, magic links, and OAuth, bundled with a Postgres database. Great when you also need data storage.
- Clerk — polished drop-in auth UI and user management, fastest to a working sign-in screen.
- Firebase Authentication — mature, generous free tier, good if you're already in the Google ecosystem.
- Roll your own — full control, but you own security, sessions, and OAuth. Only worth it for unusual requirements.
For a typical app, start with a managed provider. This guide uses Supabase because it's a common Expo pairing.
Install the dependencies
npx expo install @supabase/supabase-js @react-native-async-storage/async-storage expo-secure-store
Create the Supabase client
Persist the session so users stay logged in between app launches:
import "react-native-url-polyfill/auto"
import AsyncStorage from "@react-native-async-storage/async-storage"
import { createClient } from "@supabase/supabase-js"
export const supabase = createClient(
process.env.EXPO_PUBLIC_SUPABASE_URL,
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY,
{
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
}
)
Sign users in
Email and password is the simplest starting point:
const { error } = await supabase.auth.signInWithPassword({
email,
password,
})
To sign up instead, call supabase.auth.signUp with the same arguments.
Track the session
Listen for auth changes once, near the root of your app, and share the session through context or a store:
supabase.auth.getSession().then(({ data }) => setSession(data.session))
const { data: sub } = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
Protect your routes with Expo Router
Gate screens in a layout. Signed-out users get redirected to the sign-in screen; everyone else sees the app:
import { Redirect, Stack } from "expo-router"
export default function AppLayout() {
const { session, loading } = useAuth()
if (loading) return null
if (!session) return <Redirect href="/sign-in" />
return <Stack />
}
Put your authenticated screens under this layout and your sign-in/sign-up screens in a separate group so the redirect can't loop.
Add OAuth (optional)
Enable a provider in the Supabase dashboard, then trigger the flow with a redirect back to your app's scheme. Make sure your scheme is set in app.json — you can generate a valid one with our Expo App Config Generator.
Skip the boilerplate
If you'd rather not wire all of this up by hand, our Expo auth template ships sign-in, sign-up, session handling, protected routes, and OAuth already connected to Supabase — TypeScript-first and styled with NativeWind.