--- title: 'Build a Social Auth App with Expo React Native' description: 'Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality.' --- This tutorial demonstrates how to build a React Native app with [Expo](https://expo.dev) that implements social authentication. The app showcases a complete authentication flow with protected navigation using: - [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data with [Row Level Security](/docs/guides/auth#row-level-security) to ensure data is protected and users can only access their own information. - [Supabase Auth](/docs/guides/auth) - enables users to log in through social authentication providers (Apple and Google). ![Supabase Social Auth example](/docs/img/supabase-expo-social-auth-login.png) If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/auth/expo-social-auth). <$Partial path="project_setup.mdx" /> ## Building the app Start by building the React Native app from scratch. ### Initialize a React Native app Use [Expo](https://docs.expo.dev/get-started/create-a-project/) to initialize an app called `expo-social-auth` with the [standard template](https://docs.expo.dev/more/create-expo/#--template): ```bash npx create-expo-app@latest cd expo-social-auth ``` Install the additional dependencies: - [supabase-js](https://github.com/supabase/supabase-js) - [@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage) - A key-value store for React Native. - [expo-secure-store](https://docs.expo.dev/versions/latest/sdk/securestore/) - Provides a way to securely store key-value pairs locally on the device. - [expo-splash-screen](https://docs.expo.dev/versions/latest/sdk/splash-screen/) - Provides a way to programmatically manage the splash screen. ```bash npx expo install @supabase/supabase-js @react-native-async-storage/async-storage expo-secure-store expo-splash-screen ``` Now, create a helper file to initialize the Supabase client for both web and React Native platforms using platform-specific [storage adapters](https://docs.expo.dev/develop/user-interface/store-data/): [Expo SecureStore](https://docs.expo.dev/develop/user-interface/store-data/#secure-storage) for mobile and [AsyncStorage](https://docs.expo.dev/develop/user-interface/store-data/#async-storage) for web. {/* TODO: Future task to extract to repo and transclude */} <$CodeTabs> ```ts name=lib/supabase.web.ts import AsyncStorage from '@react-native-async-storage/async-storage'; import { createClient } from '@supabase/supabase-js'; import 'react-native-url-polyfill/auto'; const ExpoWebSecureStoreAdapter = { getItem: (key: string) => { console.debug("getItem", { key }) return AsyncStorage.getItem(key) }, setItem: (key: string, value: string) => { return AsyncStorage.setItem(key, value) }, removeItem: (key: string) => { return AsyncStorage.removeItem(key) }, }; export const supabase = createClient( process.env.EXPO_PUBLIC_SUPABASE_URL ?? '', process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY ?? '', { auth: { storage: ExpoWebSecureStoreAdapter, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, }, }, ); ``` If you want to encrypt the user's session information, use `aes-js` and store the encryption key in [Expo SecureStore](https://docs.expo.dev/versions/latest/sdk/securestore). The [`aes-js` library](https://github.com/ricmoo/aes-js) is a reputable JavaScript-only implementation of the AES encryption algorithm in CTR mode. A new 256-bit encryption key is generated using the `react-native-get-random-values` library. This key is stored inside Expo's SecureStore, while the value is encrypted and placed inside AsyncStorage. Make sure that: - You keep the `expo-secure-storage`, `aes-js` and `react-native-get-random-values` libraries up-to-date. - Choose the correct [`SecureStoreOptions`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestoreoptions) for your app's needs. E.g. [`SecureStore.WHEN_UNLOCKED`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestorewhen_unlocked) regulates when the data can be accessed. - Carefully consider optimizations or other modifications to the above example, as those can lead to introducing subtle security vulnerabilities. Implement a `ExpoSecureStoreAdapter` to pass in as Auth storage adapter for the `supabase-js` client: <$CodeTabs> ```ts name=lib/supabase.ts import { createClient } from '@supabase/supabase-js'; import { deleteItemAsync, getItemAsync, setItemAsync } from 'expo-secure-store'; const ExpoSecureStoreAdapter = { getItem: (key: string) => { console.debug("getItem", { key, getItemAsync }) return getItemAsync(key) }, setItem: (key: string, value: string) => { if (value.length > 2048) { console.warn('Value being stored in SecureStore is larger than 2048 bytes and it may not be stored successfully. In a future SDK version, this call may throw an error.') } return setItemAsync(key, value) }, removeItem: (key: string) => { return deleteItemAsync(key) }, }; export const supabase = createClient( process.env.EXPO_PUBLIC_SUPABASE_URL ?? '', process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY ?? '', { auth: { storage: ExpoSecureStoreAdapter as any, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, }, }, ); ``` ### Set up environment variables You need the API URL and the `anon` key copied [earlier](#get-the-api-keys). These variables are safe to expose in your Expo app since Supabase has [Row Level Security](/docs/guides/database/postgres/row-level-security) enabled on your database. Create a `.env` file containing these variables: <$CodeTabs> ```bash name=.env EXPO_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL EXPO_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` ### Set up protected navigation Next, you need to protect app navigation to prevent unauthenticated users from accessing protected routes. Use the [Expo `SplashScreen`](https://docs.expo.dev/versions/latest/sdk/splash-screen/) to display a loading screen while fetching the user profile and verifying authentication status. #### Create the `AuthContext` Create [a React context](https://react.dev/learn/passing-data-deeply-with-context) to manage the authentication session, making it accessible from any component: <$CodeTabs> ```tsx name=hooks/use-auth-context.tsx import { Session } from '@supabase/supabase-js' import { createContext, useContext } from 'react' export type AuthData = { session?: Session | null profile?: any | null isLoading: boolean isLoggedIn: boolean } export const AuthContext = createContext({ session: undefined, profile: undefined, isLoading: true, isLoggedIn: false, }) export const useAuthContext = () => useContext(AuthContext) ``` #### Create the `AuthProvider` Next, create a provider component to manage the authentication session throughout the app: <$CodeTabs> ```tsx name=providers/auth-provider.tsx import { AuthContext } from '@/hooks/use-auth-context' import { supabase } from '@/lib/supabase' import type { Session } from '@supabase/supabase-js' import { PropsWithChildren, useEffect, useState } from 'react' export default function AuthProvider({ children }: PropsWithChildren) { const [session, setSession] = useState() const [profile, setProfile] = useState() const [isLoading, setIsLoading] = useState(true) // Fetch the session once, and subscribe to auth state changes useEffect(() => { const fetchSession = async () => { setIsLoading(true) const { data: { session }, error, } = await supabase.auth.getSession() if (error) { console.error('Error fetching session:', error) } setSession(session) setIsLoading(false) } fetchSession() const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { console.log('Auth state changed:', { event: _event, session }) setSession(session) }) // Cleanup subscription on unmount return () => { subscription.unsubscribe() } }, []) // Fetch the profile when the session changes useEffect(() => { const fetchProfile = async () => { setIsLoading(true) if (session) { const { data } = await supabase .from('profiles') .select('*') .eq('id', session.user.id) .single() setProfile(data) } else { setProfile(null) } setIsLoading(false) } fetchProfile() }, [session]) return ( {children} ) } ``` #### Create the `SplashScreenController` Create a `SplashScreenController` component to display the [Expo `SplashScreen`](https://docs.expo.dev/versions/latest/sdk/splash-screen/) while the authentication session is loading: <$CodeTabs> ```tsx name=components/splash-screen-controller.tsx import { useAuthContext } from '@/hooks/use-auth-context' import { SplashScreen } from 'expo-router' SplashScreen.preventAutoHideAsync() export function SplashScreenController() { const { isLoading } = useAuthContext() if (!isLoading) { SplashScreen.hideAsync() } return null } ``` ### Create a logout component Create a logout button component to handle user sign-out: <$CodeTabs> ```tsx name=components/social-auth-buttons/sign-out-button.tsx import { supabase } from '@/lib/supabase' import React from 'react' import { Button } from 'react-native' async function onSignOutButtonPress() { const { error } = await supabase.auth.signOut() if (error) { console.error('Error signing out:', error) } } export default function SignOutButton() { return