diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx index 3e50b8ff57b..13cfde76466 100644 --- a/apps/docs/content/guides/getting-started.mdx +++ b/apps/docs/content/guides/getting-started.mdx @@ -305,6 +305,14 @@ hideToc: true icon: '/docs/img/icons/expo-icon', hasLightIcon: true }, + { + title: 'Expo React Native Social Auth', + href: '/guides/getting-started/tutorials/with-expo-react-native-social-auth', + description: + 'Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality.', + icon: '/docs/img/icons/expo-icon', + hasLightIcon: true + }, { title: 'Android Kotlin', href: '/guides/getting-started/tutorials/with-kotlin', diff --git a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native-social-auth.mdx b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native-social-auth.mdx index 204475ce910..2d0d1f7841c 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native-social-auth.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-expo-react-native-social-auth.mdx @@ -1,11 +1,14 @@ --- -title: 'Build an Social Auth App with Expo React Native' -description: 'Learn how to use Supabase in your React Native App for Social Authentication.' +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.' --- -<$Partial path="quickstart_intro.mdx" /> +This tutorial demonstrates how to build a product management app with social authentication. The app showcases user authentication, data management, and photo uploads using: -![Supabase Social Auth example](/docs/img/supabase-flutter-demo.png) +- [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.png) @@ -17,12 +20,11 @@ If you get stuck while working through this guide, refer to the [full example on ## Building the app -Let's start building the React Native app from scratch. +Start building the React Native app from scratch. ### Initialize a React Native app -We can 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): +You can 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 @@ -30,13 +32,13 @@ npx create-expo-app@latest cd expo-social-auth ``` -Then let's install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js) +Then install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js) ```bash npx expo install @supabase/supabase-js @react-native-async-storage/async-storage expo-secure-store expo-splash-screen ``` -Now let's create a helper file to initialize the Supabase client for web and React Native, by using a specific [storage adapter](https://docs.expo.dev/develop/user-interface/store-data/) for each platform. We will use the [Expo SecureStore](https://docs.expo.dev/develop/user-interface/store-data/#async-storage) for mobile and the [async storage](https://docs.expo.dev/develop/user-interface/store-data/#async-storage) for web. +Now create a helper file to initialize the Supabase client for both web and React Native platforms. You'll use 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. @@ -108,6 +109,9 @@ Now let's create a helper file to initialize the Supabase client for web and Rea 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) => { @@ -116,8 +120,8 @@ Now let's create a helper file to initialize the Supabase client for web and Rea } export const supabase = createClient( - process.env.EXPO_PUBLIC_SUPABASE_URL, - process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY, + process.env.EXPO_PUBLIC_SUPABASE_URL ?? '', + process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY ?? '', { auth: { storage: ExpoSecureStoreAdapter as any, @@ -134,12 +138,12 @@ Now let's create a helper file to initialize the Supabase client for web and Rea -### Create a dotenv file containing the Supabase public URL and the anon key +### Set up environment variables -We need the API URL and the `anon` key that you 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. +You need the API URL and the `anon` key that you 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. -So let's create a dotenv file containing the two variables: +Create a `.env` file containing these variables: <$CodeTabs> @@ -150,13 +154,13 @@ EXPO_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY -### Protect the navigation +### Set up protected navigation -Now we need to protect the navigation so that users can't access the protected routes without being logged in. We will use the [Expo SplashScreen](https://docs.expo.dev/versions/latest/sdk/splash-screen/) to show a loading screen while fetching the user profile and checking if the user is logged in. +Next, you need to protect your navigation to prevent unauthenticated users from accessing protected routes. You'll 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 -Now let's create [a context](https://react.dev/learn/passing-data-deeply-with-context) to manage the authentication session, so that we can access it from any component: +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> @@ -186,7 +190,7 @@ export const useAuthContext = () => useContext(AuthContext) #### Create the AuthProvider -Now let's create a provider to manage the authentication session, so that we can access it from any component: +Next, create a provider component to manage the authentication session throughout your app: <$CodeTabs> @@ -201,8 +205,11 @@ export default function AuthProvider({ children }: PropsWithChildren) { 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) { @@ -210,6 +217,26 @@ export default function AuthProvider({ children }: PropsWithChildren) { } 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 @@ -219,20 +246,18 @@ export default function AuthProvider({ children }: PropsWithChildren) { .single() setProfile(data) + } else { + setProfile(null) } setIsLoading(false) } - fetchSession() - - supabase.auth.onAuthStateChange((_event, session) => { - setSession(session) - }) - }, []) + fetchProfile() + }, [session]) return ( - {children} - + ) } ``` @@ -251,7 +276,7 @@ export default function AuthProvider({ children }: PropsWithChildren) { #### Create the SplashScreenController -Now let's create the `SplashScreenController` to use the Expo SplashScreen if the authentication session is loading, by using the `useAuthContext` hook. +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> @@ -274,9 +299,106 @@ export function SplashScreenController() { -### Set up a login component +### Create a logout component -Let's set up a basic React Native component that we will use later to handle logins and sign ups. +Create a logout button component to handle user sign-out: + +<$CodeTabs> + +```tsx name=components/social-auth-buttons/SignOutButton.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 ( +