Supabase is an open source managed back-end platform. It's a direct alternative to Firebase, which is owned by Google and closed source.
Supabase comes with features such as authentication, object storage, and managed databases. Everything is built on top of open source tools, such as PostgREST and GoTrue. If you want, you can also self-host your own instance of Supabase. As of today, Supabase is in public Beta.
In this tutorial you will learn how to build an a simple React application with authentication using Create React App (CRA). Supabase will serve as the back-end for the authentication part. The application will include sign in, sign up, and a private route than can only be accessed with valid credentials.
If you wanna jump straight to the code, you can check the GitHub repository.
Setting up Supabase
VIsit Supabase's website to create a new account. Click the "Start your project" button and sign in with your GitHub account.
After signing in to the dashboard, hit the green "New Project" button. A modal like this should appear:
Choose a name for your project and a region close to you. It's required that you set a database password too, but we won't be using any on this tutorial.
It will take a few minutes for the project to be fully created. After it's done, go to Settings > API and copy the URL & Public Anonymous API key. Save the values somewhere, you will need them later on.
π‘ Tip: To make it easier to test the sign up flow, you can also disable email confirmations on Authentication > Settings > Disable Email Confirmations.
Setting up the project
Create a new project using Create React App:
I usually do some cleanup on new CRA projects before start developing. Here's how the project structure looks like after moving files around and deleting a few imports:
Feel free to recreate the same file structure. Don't worry about adding any code or trying to make sense of all the components just yet, we will go through everything later.
The src/index.js and src/components/App.js were already created by CRA. Here's how they look after cleaning up:
Setting up the Supabase Client Library
First, install the Supabase JavaScript client library on your project:
Now add the code to initialize Supabase on src/supabase.js:
In your .env.local file, add the URL and Public Anonymous API Key saved from the first step:
Create authentication pages
Let's write the code for the Signup, Login and Dashboard components. These will be the three main pages of the application.
For now, let's just focus on writing a boilerplate for those components, without any authentication logic. Start by writing the Signup component:
The Login component looks very similar to Signup, with a few differences:
The Dashboard is a simple component that displays a greeting message and offers the user to sign out:
Routing components with React Router
So far the components are isolated. There is no routing between the Signup, Login and Dashboard pages.
Let's work on that by adding React Router to the project:
In src/components/App.js, declare a route for each of the components created before:
Let's also add links to navigate between the Signup and Login components:
You can test the navigation between components by running the project and clicking on the links or changing the URL in the navigation bar:
Shameless plug: you may notice that the HTML looks a bit different, that's because I am using axist, a tiny drop-in CSS library that I built.
Adding the authentication logic
To set up the authentication logic for the app, we're going to use React's Context API.
The Context API allows sharing data to a tree of components without explicitly passing props through every level of the tree. It's used to share data that is considered "global" (within that component tree).
In this tutorial, we will use Context to share data associated with the user and the authentication operations. All this information will come from Supabase and will be needed on multiple parts of the app.
Let's start by adding code on src/contexts/Auth.js. First, create a Context object:
Now, in the same file, create a Provider component called AuthProvider:
The AuthProvider does three things:
Calls supabase.auth.session to find out the current state of the user and update the user object.
Listens for changes in the authentication state (user signed in, logged out, created a new account, etc.) by subscribing to supabase.auth.onAuthStateChange function.
Prepares the object that will be shared by its children components (value prop). In this case, any components down the tree will have access to the signUp, signIn, signOut functions and the user object. They will be used by the Signup, Login and Dashboard components later on.
The loading state property will make sure the child components are not rendered before we know anything about the current authentication state of the user.
Now, create a useAuth function to help with accessing the context inside the children components:
You can check how the src/contexts/Auth.js looks after all the changes on the GitHub repository.
Lastly, we need to wrap the Signup, Login and Dashboard components with the AuthProvider:
Adding authentication to the components
Remember the @TODOs you left earlier in the components? Now it's time to, well, do them.
The functions needed by the components - signUp, signIn and signOut - as well as the user object are available through the Context. We can now get those values using the useAuth function.
Let's start by adding the sign up logic to the Signup component:
The Login component will look very similar. The main difference is that you will call signIn instead of signUp:
Lastly, change the Dashboard so the user can sign out of the application. You can also display some basic information together with the greeting message, such as the user ID:
Protecting routes
Currently, all the authentication logic is in place, but the Dashboard component remains publicly accessible. Anyone who happens to fall on locahost:3000 would see a broken version of the dashboard.
Let's fix that by protecting the route. If a user that is not authenticated tries to access it, they will be redirected to the login page.
Start by creating a PrivateRoute component:
The PrivateRoute wraps the Route component from React Router and passes down the props to it. It only renders the page if the user object is not null (the user is authenticated).
If the userobject is empty, a redirect to the login page will be made by Redirect component from React Router.
Finally, update the dashboard route in the App component to use a PrivateRoute instead:
Done! The dashboard is only available for authenticated users.
Final result
This is how the final version of the application should look like:
You can see the sign up, login, and sign out working. The dashboard page is also protected, attempting to access it by changing the URL redirects the user to the login page. Notice the user ID showing there too.
Going further
There are a few things that we could add for a more complete authentication flow:
Password reset. I intentionally left it out for simplicity, but Supabase supports password reset through their API. It does all the heavy-lifting for you, including sending the email to the user with the reset instructions.
Authentication providers. When logging in, you can also specify different authentication providers like Google, Facebook and GitHub. Check out the docs.
User operations. You can also add metatada to the authenticated user using the update function. With this, you could build for example a basic user profile page.