Authentication with Supabase and React
Tags: supabase, react
Published: 2021-04-07 17:30:11

---
title: 'Authentication with Supabase and React'
published: true
description: Authentication with Supabase and React'
description: Supabase is an open source managed backend platform. It's a direct alternative Firebase, which is owned by Google...
tags:
  - supabase
  - react
cover_image: https://hyperfoo.io/images/supabase.png
canonical_url: https://ruanmartinelli.com/posts/supabase-authentication-react
---

[Supabase](https://supabase.io) 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](https://postgrest.org/en/stable/) and [GoTrue](https://github.com/netlify/gotrue). If you want, you can also [self-host your own instance](https://supabase.io/docs/guides/self-hosting) 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](https://github.com/facebook/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](https://github.com/ruanmartinelli/supabase-auth-react).

## Setting up Supabase

VIsit [Supabase's website](https://supabase.com) 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:

<img src="https://hyperfoo.io/images/supabase-auth-1.png" class="shadow-lg rounded-sm mb-4 mx-auto" />
<figcaption>1. Create a new project on Supabase</figcaption>

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.

<img src="https://hyperfoo.io/images/supabase-auth-2.png" class="shadow-lg rounded-sm mb-4 mx-auto max-w-xl" />
<figcaption>2. Copy public credentials</figcaption>

> 💡 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:

```bash
npx create-react-app supabase-auth-react
```

<figcaption>3. Create a new CRA project</figcaption>

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:

```bash
.
├── package.json
├── .env.local
└── src
    ├── components
    │   ├── App.js # Moved from src/
    │   ├── Dashboard.js
    │   ├── Login.js
    │   ├── PrivateRoute.js
    │   └── Signup.js
    ├── contexts
    │   └── Auth.js
    ├── index.js # Already created by CRA
    └── supabase.js
```

<figcaption>4. Overview of the folder structure after cleaning up CRA files</figcaption>

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:

```jsx
// src/components/App.js

export function App() {
  return (
    <div>
      <h1>supabase-auth-react</h1>
    </div>
  )
}
```

<figcaption>5. Updated App.js component</figcaption>

```jsx
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'

import { App } from './components/App'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)
```

<figcaption>6. Updated root component</figcaption>

## Setting up the Supabase Client Library

First, install the Supabase JavaScript client library on your project:

```bash
npm install @supabase/supabase-js
```

<figcaption>7. Install the Supabase client</figcaption>

Now add the code to initialize Supabase on `src/supabase.js`:

```jsx
// src/supabase.js

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.REACT_APP_SUPABASE_URL,
  process.env.REACT_APP_SUPABASE_PUBLIC_KEY
)

export { supabase }
```

<figcaption>8. Supabase client initalization</figcaption>

In your `.env.local` file, add the URL and Public Anonymous API Key saved from the first step:

```bash
# .env.local

REACT_APP_SUPABASE_URL="https://YOUR_SUPABASE_URL.supabase.co"
REACT_APP_SUPABASE_PUBLIC_KEY="eyJKhbGciOisJIUzI1Nd2iIsInR5cCsI6..."
```

<figcaption>9. Set Supabase configuration variables</figcaption>

## 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:

```jsx
// src/components/Signup.js

import { useRef, useState } from 'react'

export function Signup() {
  const emailRef = useRef()
  const passwordRef = useRef()

  async function handleSubmit(e) {
    e.preventDefault()

    // @TODO: add sign up logic
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <label htmlFor="input-email">Email</label>
        <input id="input-email" type="email" ref={emailRef} />

        <label htmlFor="input-password">Password</label>
        <input id="input-password" type="password" ref={passwordRef} />

        <br />

        <button type="submit">Sign up</button>
      </form>
    </>
  )
}
```

<figcaption>10. Initial code for the Signup component</figcaption>

The `Login` component looks very similar to `Signup`, with a few differences:

```jsx
// src/components/Login.js

import { useRef, useState } from 'react'

export function Login() {
  const emailRef = useRef()
  const passwordRef = useRef()

  async function handleSubmit(e) {
    e.preventDefault()

    // @TODO: add login logic
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <label htmlFor="input-email">Email</label>
        <input id="input-email" type="email" ref={emailRef} />

        <label htmlFor="input-password">Password</label>
        <input id="input-password" type="password" ref={passwordRef} />

        <br />

        <button type="submit">Login</button>
      </form>
    </>
  )
}
```

<figcaption>11. Initial code for the Login component</figcaption>

The `Dashboard` is a simple component that displays a greeting message and offers the user to sign out:

```jsx
// src/components/Dashboard.js

export function Dashboard() {
  async function handleSignOut() {
    // @TODO: add sign out logic
  }

  return (
    <div>
      <p>Welcome!</p>
      <button onClick={handleSignOut}>Sign out</button>
    </div>
  )
}
```

<figcaption>12. Initial code for the Dashboard component</figcaption>

## 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](https://reactrouter.com/) to the project:

```bash
npm install react-router-dom
```

<figcaption>13. Install React Router</figcaption>

In `src/components/App.js`, declare a route for each of the components created before:

```jsx
// src/components/App.js

import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'

import { Signup } from './Signup'
import { Login } from './Login'
import { Dashboard } from './Dashboard'

export function App() {
  return (
    <div>
      <h1>supabase-auth-react</h1>

      {/* Add routes here👇 */}
      <Router>
        <Switch>
          <Route exact path="/" component={Dashboard} />
          <Route path="/signup" component={Signup} />
          <Route path="/login" component={Login} />
        </Switch>
      </Router>
    </div>
  )
}
```

<figcaption>14. Declare routes in the App component</figcaption>

Let's also add links to navigate between the `Signup` and `Login` components:

```jsx
// src/components/Signup.js

import { Link } from 'react-router-dom'

export function Signup() {
  // ...

  return (
    <>
      <form onSubmit={handleSubmit}>{/* ... */}</form>

      <br />

      {/* Add this 👇 */}
      <p>
        Already have an account? <Link to="/login">Log In</Link>
      </p>
    </>
  )
}
```

<figcaption>15. Add link between Signup and Login pages</figcaption>

```jsx
// src/components/Login.js

import { Link } from 'react-router-dom'

export function Login() {
  // ...

  return (
    <>
      <form onSubmit={handleSubmit}>{/* ... */}</form>

      <br />

      {/* Add this 👇 */}
      <p>
        Don't have an account? <Link to="/signup">Sign Up</Link>
      </p>
    </>
  )
}
```

<figcaption>16. Add link between Signup and Login pages</figcaption>

You can test the navigation between components by running the project and clicking on the links or changing the URL in the navigation bar:

<img src="https://hyperfoo.io/images/supabase-auth-3.gif" class="shadow-lg rounded-sm mb-4 mx-auto" />

<figcaption>17. Demo of the navigation working</figcaption>

> Shameless plug: you may notice that the HTML looks a bit different, that's because I am using [axist](https://ruanmartinelli.github.io/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).

You can read more about React Context in the [official documentation](https://reactjs.org/docs/context.html).

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:

```jsx
// src/contexts/Auth.js

import React, { useContext, useState, useEffect } from 'react'
import { supabase } from '../supabase'

const AuthContext = React.createContext()

// ...
```

<figcaption>18. Create the Context object</figcaption>

Now, in the same file, create a Provider component called `AuthProvider`:

```jsx
// src/contexts/Auth.js

// ...

export function AuthProvider({ children }) {
  const [user, setUser] = useState()
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Check active sessions and sets the user
    const session = supabase.auth.session()

    setUser(session?.user ?? null)
    setLoading(false)

    // Listen for changes on auth state (logged in, signed out, etc.)
    const { data: listener } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        setUser(session?.user ?? null)
        setLoading(false)
      }
    )

    return () => {
      listener?.unsubscribe()
    }
  }, [])

  // Will be passed down to Signup, Login and Dashboard components
  const value = {
    signUp: (data) => supabase.auth.signUp(data),
    signIn: (data) => supabase.auth.signIn(data),
    signOut: () => supabase.auth.signOut(),
    user,
  }

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  )
}

// ...
```

<figcaption>19. Create the AuthProvider</figcaption>

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:

```jsx
// src/contexts/Auth.js

// ...

export function useAuth() {
  return useContext(AuthContext)
}
```

<figcaption>20. Create the useAuth function</figcaption>

You can check how the `src/contexts/Auth.js` looks after all the changes on the [GitHub repository](https://github.com/ruanmartinelli/supabase-auth-react/blob/main/src/contexts/Auth.js).

Lastly, we need to wrap the `Signup`, `Login` and `Dashboard` components with the `AuthProvider`:

```jsx
// src/components/App.js

// ...

import { AuthProvider } from '../contexts/Auth'

export function App() {
  return (
    <div>
      <h1>supabase-auth-react</h1>
      <Router>
        {/* Wrap routes in the AuthProvider 👇 */}
        <AuthProvider>
          <Switch>
            <PrivateRoute exact path="/" component={Dashboard} />
            <Route path="/signup" component={Signup} />
            <Route path="/login" component={Login} />
          </Switch>
        </AuthProvider>
      </Router>
    </div>
  )
}
```

<figcaption>21. Add the AuthProvider to App.js</figcaption>

## Adding authentication to the components

Remember the `@TODO`s 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:

```jsx
// src/components/Signup.js

import { useRef, useState } from 'react'
import { useHistory, Link } from 'react-router-dom'

import { useAuth } from '../contexts/Auth'

export function Signup() {
  const emailRef = useRef()
  const passwordRef = useRef()

  // Get signUp function from the auth context
  const { signUp } = useAuth()

  const history = useHistory()

  async function handleSubmit(e) {
    e.preventDefault()

    // Get email and password input values
    const email = emailRef.current.value
    const password = passwordRef.current.value

    // Calls `signUp` function from the context
    const { error } = await signUp({ email, password })

    if (error) {
      alert('error signing in')
    } else {
      // Redirect user to Dashboard
      history.push('/')
    }
  }

  return (
    <>
      <form onSubmit={handleSubmit}>{/* ... */}</form>

      <br />

      <p>
        Already have an account? <Link to="/login">Log In</Link>
      </p>
    </>
  )
}
```

<figcaption>22. Add authentication logic to the Signup component</figcaption>

The `Login` component will look very similar. The main difference is that you will call `signIn` instead of `signUp`:

```jsx
// src/components/Login.js

import { useRef, useState } from 'react'
import { useHistory, Link } from 'react-router-dom'

import { useAuth } from '../contexts/Auth'

export function Login() {
  const emailRef = useRef()
  const passwordRef = useRef()

  // Get signUp function from the auth context
  const { signIn } = useAuth()

  const history = useHistory()

  async function handleSubmit(e) {
    e.preventDefault()

    // Get email and password input values
    const email = emailRef.current.value
    const password = passwordRef.current.value

    // Calls `signIn` function from the context
    const { error } = await signIn({ email, password })

    if (error) {
      alert('error signing in')
    } else {
      // Redirect user to Dashboard
      history.push('/')
    }
  }

  return (
    <>
      <form onSubmit={handleSubmit}>{/* ... */}</form>

      <br />

      <p>
        Don't have an account? <Link to="/signup">Sign Up</Link>
      </p>
    </>
  )
}
```

<figcaption>23. Add authentication logic to the Login component</figcaption>

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:

```jsx
// src/components/Dashboard.js

import { useHistory } from 'react-router'
import { useAuth } from '../contexts/Auth'

export function Dashboard() {
  // Get current user and signOut function from context
  const { user, signOut } = useAuth()

  const history = useHistory()

  async function handleSignOut() {
    // Ends user session
    await signOut()

    // Redirects the user to Login page
    history.push('/login')
  }

  return (
    <div>
      {/* Change it to display the user ID too 👇*/}
      <p>Welcome, {user?.id}!</p>
      <button onClick={handleSignOut}>Sign out</button>
    </div>
  )
}
```

<figcaption>24. Add authentication logic to the Dashboard component</figcaption>

## 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:

```jsx
// src/components/PrivateRoute.js

import React from 'react'
import { Route, Redirect } from 'react-router-dom'

import { useAuth } from '../contexts/Auth'

export function PrivateRoute({ component: Component, ...rest }) {
  const { user } = useAuth()

  return (
    <Route
      {...rest}
      render={(props) => {
        // Renders the page only if `user` is present (user is authenticated)
        // Otherwise, redirect to the login page
        return user ? <Component {...props} /> : <Redirect to="/login" />
      }}
    ></Route>
  )
}
```

<figcaption>25. Create the PrivateRoute component</figcaption>

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 `user`object 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:

```diff
// src/components/App.js

- <Route exact path="/" component={Dashboard} />
+ <PrivateRoute exact path="/" component={Dashboard} />
```

<figcaption>26. Update the Dashboard component to use PrivateRoute</figcaption>

Done! The dashboard is only available for authenticated users.

## Final result

This is how the final version of the application should look like:

<img src="https://hyperfoo.io/images/supabase-auth-4.gif" class="shadow-lg rounded-sm mb-4 mx-auto" />

<figcaption>26. Final demo of the application</figcaption>

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](https://supabase.io/docs/reference/javascript/reset-password-email) 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](https://supabase.io/docs/reference/javascript/auth-signin).

**User operations**. You can also add metatada to the authenticated user using the [update function](https://supabase.io/docs/reference/javascript/auth-update). With this, you could build for example a basic user profile page.

Thanks for reading this far!