DEV Community

Cover image for Sign in with supabase
Lucas Frazao
Lucas Frazao

Posted on

Sign in with supabase

Hello, I'm Lucas Frazao. I've been working as a software engineer (SWE) for the past five years.

In this post, we'll build a simple authentication flow using ReactJS (v.18.3) and Supabase. Building authentication flows is a common task in personal projects. So, I'd like to show you how to achieve this using supabase authentication.

What is supabase?

Supabase is an open source Firebase alternative that uses PostgreSQL. This means it can be used for various purposes beyond authentication, such as storing and organizing images and data, building real-time chat applications, managing user authentication, and more. You can find more information in the official documentation: https://supabase.com/docs

Let's start

To keep things simple, we will be using vitejs for our development enviroment, npm as the package manager, and typescript because... well, I like TS!

Disclaimer: This tutorial can be followed using either TypeScript or JavaScript.

So, first of all, we need a web application to connect with supabase, so let's do this.

Run command:

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

The project was named as ''auth-supabase'' but you can choose another name.
Let's give a name to project:

npm create vite@latest
? Project name: › auth-supabase
Enter fullscreen mode Exit fullscreen mode

Select react on framework options:

✔ Project name: … auth-supabase
? Select a framework: › - Use arrow-keys. Return to submit.
   Vanilla
   Vue
❯  React
   Preact
   Lit
   Svelte
   Solid
   Qwik
   Others
Enter fullscreen mode Exit fullscreen mode

Select TypeScript (or JavaScript) from the variant options.

✔ Project name: … auth-supabase
✔ Select a framework: › React
? Select a variant: › - Use arrow-keys. Return to submit.
❯  TypeScript
   TypeScript + SWC
   JavaScript
   JavaScript + SWC
   Remix ↗
Enter fullscreen mode Exit fullscreen mode

Let's navigate to the project folder and clean things up a bit.
Run the following command:

cd auth-supabase && code .
Enter fullscreen mode Exit fullscreen mode

You should see something like this:
vscode interaction

Now, to streamline the project structure, we'll perform some cleanup steps. Here's what to do:
-> Delete the src/assets folder.
-> Clear the content of index.css and paste the following code:

*,
:after,
:before {
    box-sizing: border-box;

}

body {
    margin: 0;
    min-height: 100vh;
    scroll-behavior: smooth;
    text-rendering: optimizeSpeed;
    font-synthesis: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;

}

a, button {
    color: inherit;
    cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

-> Clear the contents of App.tsx then past the following code:

import { FormEvent } from "react";
import "./App.css";

export default function App() {
    function handleSubmit(event: FormEvent<HTMLFormElement>) {
        event.preventDefault();

        const data = new FormData(event.currentTarget);
        const email = data.get("email")?.toString();
        const password = data.get("password")?.toString();

        console.log({ email, password });
    }

    return (
        <main>
            <h1>Sign in with supabase</h1>
            <form onSubmit={handleSubmit}>

                <label htmlFor="email">E-mail</label>
                <input 
                    type="text" 
                    name="email" 
                    placeholder="email@email.com" 
                    required 
                />

                <label htmlFor="password">Password</label>
                <input
                    type="password"
                    name="password"
                    placeholder="********"
                    required
                /> 

                <button type="submit">Login</button>
            </form>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

It's necessary install the dependencies running:

npm install 
Enter fullscreen mode Exit fullscreen mode

Great! If you didn't encounter any errors during the previous steps, we can move on to configuring supabase in our project. While this tutorial will focus on a traditional username and password login, supabase also supports social logins. If you'd like to explore that option, you can refer to the official documentation here:
https://supabase.com/docs/guides/auth/social-login

To install supabase, run command:

npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode

We can verify if our project is set up correctly by checking package.json
package.json image

Once we've completed these steps, run the following command to start the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Then, you can access your application in the browser by visiting http://localhost:5173/ . You should see something like this:

web server image

Let's configure our auth. Remember, it's necessary to create an account on supabase to follow up this post.

-> Select new organization
option to create new organization

-> Click "Create organization" after filling out the form.
form to create a new organization

-> To create a new project, fill out all the fields in this form. For the database password, you can click "Generate a Password" and then "Copy" to securely store the generated password. This password is critical for managing your project, so be sure to keep it safe.
form to create a new project

You don't need to change the security options for now. Just click "Create new project" to proceed.
Now, the project is setting up.
image of status setting up

-> In the sidebar menu, locate the "Authentication" option and click on it.
supabase sidebar

-> From the user management options, select "Create New User" to add a new user.

Disclaimer: While this tutorial demonstrates creating a new user directly, it's generally recommended to prioritize security in real-world applications. For increased security, consider using the "send invitation" option, which allows users to self-register and verify their email addresses before gaining access. Additionally, explore alternative authentication methods like social logins or passwordless authentication to best suit your specific security needs.
menu opened to create new user

-> Enter your email address and password. Then, select "Create user" and make sure to check "Auto Confirm User?" .
form to create a new user

The expected result:
database with current users

So, now we have an user to test the connection between supabase and our application. Let's navigate back to the home page and select the "Connect" option.

-> Click on "Connect", on right side.
home page from supabase

-> Copy all content in this code block
code block with enviroment variables

-> In our application, create a .env file, then paste the code on it:
.env file on vscode

In Vite.js, environment variables are prefixed with VITE_ by default. You can find more details about environment variables and modes in the official Vite documentation:
https://vitejs.dev/guide/env-and-mode

Now, let's create a utility function to connect with Supabase. Here's what to do:

  • Create a new folder: Inside the src directory, create a new folder named utils.
  • Create a TypeScript file: Inside the newly created utils folder, create a new file named supabase.ts.
  • Add the following code: Paste the code snippet you want to add to supabase.ts below.
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);
Enter fullscreen mode Exit fullscreen mode

function supabase

Now that we've finished configuring everything, let's move on to final stage.

Final stage

initial handleSubmit

We already have the email and password values stored in variables, and they're currently being used for logging and potentially within the handleSubmit(), but let's make some changes. Let's refactor this function to be asynchronous and incorporate a try-catch block.

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const data = new FormData(event.currentTarget);
    const email = data.get("email")?.toString();
    const password = data.get("password")?.toString();

    try {
        console.log({ email, password });
    } catch (error) {
        console.error(error);
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of using try-catch with form submissions and API calls:

  • Prevents unexpected crashes: By catching potential errors, you can gracefully handle them and avoid disrupting the user experience.
  • Custom error handling: The try-catch block allows you to provide informative error messages to the user in case of issues during form submission or API interaction.

Great improvement! Building on error handling, let's add some basic validation before the try-catch block to prevent undefined values from causing errors.

if (!email || !password) return;
Enter fullscreen mode Exit fullscreen mode

handle submit with handler error

Now, let's import supabase function from our supabase.ts into the src/App.tsx file. Then, we can utilize it within the try-catch block of the handleSubmit function.

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

/* code ... **/
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const data = new FormData(event.currentTarget);
    const email = data.get("email")?.toString();
    const password = data.get("password")?.toString(); 

    if (!email || !password) return;

    try {
        const {} = await supabase.auth.signInWithPassword({ email, password });
    } catch (error) {
        console.error(error);
    }
}
Enter fullscreen mode Exit fullscreen mode

signInWithPassword example

It's possible to use two properties from signInWithPassword(), error and data, we'll use both, data to set token on application and error to return an error to user. Look, we changed the name of data to response, because we already use a variable called data.

const { error, data: response } = await supabase.auth.signInWithPassword({
    email,
    password,
});
Enter fullscreen mode Exit fullscreen mode

Now that we've incorporated a try-catch block and have access to the error object, let's add a conditional statement. This statement will determine whether to throw the error or set the token in local storage based on the outcome of the signInWithPassword function.

async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const data = new FormData(event.currentTarget);
    const email = data.get("email")?.toString();
    const password = data.get("password")?.toString();

    if (!email || !password) return;

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

        if (error) {
            throw new Error(error.message);
        } else {
            const accessToken = response.session?.access_token;
            localStorage.setItem("access_token", accessToken);
        }
    } catch (error) {
        console.error("occured an error:", error);
    }
}
Enter fullscreen mode Exit fullscreen mode

To verify a successful login, we can check your browser's local storage. Here's how:

  1. Open the developer tools:
    • Windows: Press Ctrl + Shift + J simultaneously.
    • macOS: Press Command (⌘) + Option (⌥) + J simultaneously.
  2. Navigate to the "Application" tab.
  3. In the left sidebar, locate the "Local Storage" section.
  4. Look for a key named "access_token". The presence of this key and its corresponding value indicate a successful login. You should see something like this:

local storage after login

Bonus

Now that we've established the sign-in workflow, let's explore how to implement sign-out functionality.
The sign-out process shares a lot in common with handleSubmit(). In essence, they follow a similar pattern or approach. Check the code example below for a detailed implementation.

async function handleSignOut() {
    try {
    const { error } = await supabase.auth.signOut();

    if (error) {
        throw new Error(error.message);
    }

    } catch (error) {
        console.error(error);
    }
}
Enter fullscreen mode Exit fullscreen mode

It's important to built a button to call signOut function.

<button type="button" onClick={handleSignOut}>
    sign out
</button>
Enter fullscreen mode Exit fullscreen mode

code from form

form with sign out button

After clicking the sign-out button, you can verify that the sign-out process was successful by checking your browser's local storage. Since Supabase uses local storage to store authentication tokens, a successful sign-out will result in the deletion of the corresponding token object.

after and before sign out

Remember, the key named access_token was created by us to store token. After signing out, we could to remove this key to ensure a clean and secure session termination. Here's a validate you can add within the handleSignOut() function to achieve this:

// Add this if you want to clear all localStorage
if (error) {
    throw new Error(error.message);
} else {
    localStorage.clear()
}

// Add this if you want to delete an especific key
if (error) {
    throw new Error(error.message);
} else {
    localStorage.removeItem('access_token')
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

We've built a secure user authentication flow using Supabase in our application. This guide covered essential steps like supabase initialization, sign-in, and sign-out with local storage management. Now you have a solid foundation for user authentication - feel free to customize it for your project's needs and explore more supabase functionalities!

Top comments (0)