Supabase Authentication with Expo React Native: A Complete Guide

Adding authentication is an essential part of many React Native applications. Instead of building authentication from scratch, you can use Supabase Authentication to manage user accounts, sign-up, sign-in, and sign-out in your Expo React Native application.

In this tutorial, we'll learn how to add Supabase Auth to an Expo app and build a simple email and password authentication flow. We'll start by connecting an Expo application to a Supabase project and configuring the Supabase client. Then, we'll implement user registration, sign-in, and sign-out using the Supabase JavaScript client.

We'll also look at the authenticated user returned by Supabase and learn how to access information such as the user's unique ID and email address. This user ID is particularly useful when connecting authenticated users to application data stored in a Supabase PostgreSQL database.

Finally, we'll configure authentication session persistence so that users can remain signed in when they close and reopen the application. By the end of this tutorial, you'll have a working Supabase authentication flow in Expo where users can create an account, sign in, remain authenticated, view their user information, and sign out.

Prerequisites

This tutorial assumes that you have an Expo React Native project and a Supabase project ready to use.

If you're new to Expo or need help creating and setting up an Expo React Native project, you can follow my Expo course, which covers the fundamentals of setting up and working with Expo.

If you are new to Supabase or haven't set up your PostgreSQL database yet, start with Supabase Tutorial: Getting Started with the SQL Editor and PostgreSQL. It covers creating a Supabase project, working with the SQL Editor, creating database tables, configuring permissions, and setting up Row Level Security (RLS).
Once your Expo and Supabase projects are ready, you can continue with this tutorial to connect them and implement authentication.

Connecting Expo to Supabase

Before we implement authentication, we first need to connect our Expo application to our Supabase project. Supabase provides a JavaScript client that allows our React Native application to communicate with Supabase services such as Authentication and the database. If you already have a Supabase project from the previous tutorial, you can use the same project for this application.

Installing the Supabase Client

First, install the Supabase JavaScript client:

npx expo install @supabase/supabase-js

Because we're building a React Native application, we'll also install AsyncStorage. We'll use it to persist the Supabase authentication session on the device.

npx expo install @react-native-async-storage/async-storage

We won't use AsyncStorage directly in our authentication code. Instead, we'll provide it to the Supabase client so that Supabase can store and restore the authentication session for us.

Configuring Environment Variables

Our Supabase client needs two values to connect to our project: the Supabase project URL and the publishable key.
Instead of writing these values directly in our source code, we'll store them in environment variables. Create a .env file in the root of your Expo project:

EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
EXPO_PUBLIC_SUPABASE_KEY=your_supabase_publishable_key

You can find your project's URL and publishable key in the Connect section of your Supabase dashboard.
The EXPO_PUBLIC_ prefix is important because Expo makes variables with this prefix available to the application.

Important: Never put your Supabase service role key in a React Native or Expo application. The service role key has elevated privileges and should remain on a trusted server.

Creating the Supabase Client

Now create a lib/supabase.ts file:


import AsyncStorage from "@react-native-async-storage/async-storage";
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.EXPO_PUBLIC_SUPABASE_KEY!;

export const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

  

Let's briefly look at what this configuration does.

The createClient() function creates the Supabase client that we'll use throughout our application. We pass it our Supabase project URL and publishable key so that the application knows which Supabase project it should communicate with.

The ! at the end of each variable is a TypeScript non-null assertion. It tells TypeScript that we expect these environment variables to be defined.

The storage option tells Supabase where to persist the authentication session. In our Expo application, we're using AsyncStorage for this purpose.

The persistSession: true option enables session persistence, while autoRefreshToken: true allows Supabase to automatically refresh the session when necessary.

We set detectSessionInUrl to false because this application is running as a native React Native/Expo application rather than relying on browser URL-based session detection.

We'll look at how the persisted session is restored and synchronized with our application's state later in the tutorial.

Using the Supabase Client

Once the client is configured, we can import it into our Expo components:

import { supabase } from "./lib/supabase";

We can then use supabase to interact with Supabase Authentication:

const { data, error } = await supabase.auth.signInWithPassword({ email, password, });

We'll explore this authentication method in the next section.
At this point, our Expo application is connected to Supabase and the client is configured to support persistent authentication sessions. We can now start building the authentication flow.

Setting Up Authentication

Now that our Expo application is connected to Supabase, we can set up authentication. Supabase Authentication provides the tools we need to manage user accounts and authenticate users without having to build an authentication system from scratch.

In this tutorial, we'll use email and password authentication. Users will be able to create an account, sign in with their credentials, and sign out of the application.

Before writing the authentication code, make sure that the Email provider is enabled in your Supabase project. In the Supabase dashboard, open Authentication and check the Providers section. Make sure that the Email provider is enabled.

Once the provider is configured, we can start implementing the authentication flow in our Expo application.


async function signUp() {
  if (!email || !password) {
    Alert.alert("Error", "Please enter email and password");
    return;
  }

  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  });

  if (error) {
    Alert.alert("Signup failed", error.message);
    return;
  }

  setUser(data.user);

  Alert.alert("Success", "Account created!");
}

async function signIn() {
  if (!email || !password) {
    Alert.alert("Error", "Please enter email and password");
    return;
  }

  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    Alert.alert("Login failed", error.message);
    return;
  }

  console.log("USER ID:", data.user.id);
  console.log("USER EMAIL:", data.user.email);

  setUser(data.user);

  Alert.alert("Success", "You are logged in!");
}

  

Persisting and Restoring the Authentication Session

When a user signs in, Supabase creates an authentication session. We want this session to remain available even if the user closes and reopens the application. Otherwise, the user would have to sign in every time they launch the app.
Earlier, we configured the Supabase client to use AsyncStorage and enabled session persistence:


auth: {
  storage: AsyncStorage,
  autoRefreshToken: true,
  persistSession: true,
  detectSessionInUrl: false,
}

This allows Supabase to persist the authentication session on the device. However, our React application's user state is separate from this persisted session. When the application starts, we need to check whether a session already exists and update our state accordingly.

We can do this with getSession() inside a useEffect:


useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => {
    setUser(session?.user ?? null);
  });
}, []);
  

When the component loads, getSession() checks whether Supabase has an existing session. If a session exists, we get the authenticated user from session.user and store it in our user state.

We can also listen for authentication changes so that our application's state stays synchronized with Supabase:


useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => {
    setUser(session?.user ?? null);
  });

  const {
    data: { subscription },
  } = supabase.auth.onAuthStateChange((_event, session) => {
    setUser(session?.user ?? null);
  });

  return () => {
    subscription.unsubscribe();
  };
}, []);

  

onAuthStateChange() listens for changes to the user's authentication state. Whenever the user signs in, signs out, or their session changes, Supabase provides the current session. We use session?.user ?? null to update our user state so that the application's UI stays synchronized with the authentication state.

Finally, we unsubscribe from the listener when the component is unmounted. This prevents the listener from remaining active when it is no longer needed.

Connecting Authentication to the Database

Once a user is authenticated, Supabase provides a unique ID through user.id. This ID can be used to associate database records with the authenticated user. For example, a todos table can have a user_id column that stores the ID of the user who created each todo. Combined with Row Level Security (RLS), this allows you to restrict database access so users can only access records that belong to them. For a detailed explanation of creating tables, configuring RLS policies, and working with the Supabase PostgreSQL database, see the previous tutorial.

Conclusion

In this tutorial, we connected an Expo React Native application to Supabase and built a basic authentication flow using Supabase Auth. We implemented email and password sign-up, sign-in, and sign-out, accessed the authenticated user's information, and configured session persistence so users can remain signed in when they reopen the application.

Authentication is often the first step toward building applications with user-specific data. Once you have an authenticated user, the user's unique ID can be used to associate records in your Supabase database with that user and protect those records with Row Level Security (RLS).

If you haven't worked with Supabase PostgreSQL and RLS yet, check out the previous tutorial, where we cover creating a Supabase project, working with the SQL Editor, creating database tables, configuring permissions, and setting up Row Level Security.

Now that you know how to authenticate users with Supabase in an Expo React Native application, the next step is working with data. In the Supabase Database with Expo React Native article, you'll learn how to create database tables, perform CRUD operations, and use Row Level Security (RLS) to control which data authenticated users can access.