BlogHome
Nuxt 3 authentication with Supabase

Nuxt 3 authentication with Supabase

Jan 31, 2023

Supabase is the open-source Firebase alternative that presents us with back-end features such as a Postgres database, authentication, Edge functions, Storage, and Real-time subscriptions that we can use to build applications.

The interest of this tutorial is the authentication part, and, we are going to learn how to authenticate Nuxt 3 applications using Supabase.

To accomplish the task in this tutorial you need the latest LTS version of Node.js installed. And, the source code to the project being created in this tutorial is available in this - GitHub repository.

Setting up the Nuxt project

If you are new to Nuxt 3 the getting started with Nuxt 3 tutorial will be a good place to start. Otherwise, run the following script on your terminal to scaffold a new Nuxt project.

npx nuxi init nuxt3-supabase-auth

The above script will scaffold a new Nuxt 3 app. Get into the project directory - cd nuxt-supabase-auth and create the following pages and components.

The login and index pages

Delete the default App.vue component scaffolded with the install script since it's of no use in this app's setup.

Create a /pages directory and in it add 3 page components - index.vue, register.vue, and login.vue. Add the following code to each respective page component.

Starting with the login.vue component, add the following code.

<script setup>
  const credentials = reactive({
    email: '',
    password: '',
  });
</script>

<template>
  <h1>Log in to your account!</h1>
  <form>
    <div>
      <label for="email">Email</label>
      <input type="email" id="email" v-model="credentials.email" />
    </div>
    <div>
      <label for="password">Password</label>
      <input type="password" id="password" v-model="credentials.password" />
    </div>
    <div>
      <button type="submit">Submit</button>
    </div>
  </form>
</template>

As for the register.vue component, add the following code.

<script setup>
  const credentials = reactive({
    firstName: '',
    surname: '',
    email: '',
    password: '',
    passwordRepeat: '',
  });
</script>

<template>
  <h1>Create an account!</h1>
  <form>
    <div>
      <label for="first-name">First Name</label>
      <input type="text" id="first-name" v-model="credentials.firstName" />
    </div>
    <div>
      <label for="surname">Surname</label>
      <input type="text" id="surname" v-model="credentials.surname" />
    </div>
    <div>
      <label for="email">Email</label>
      <input type="email" id="email" v-model="credentials.email" />
    </div>
    <div>
      <label for="password">Password</label>
      <input type="password" id="password" v-model="credentials.password" />
    </div>
    <div>
      <label for="password">Repeat Password</label>
      <input
        type="password"
        id="repeat-password"
        v-model="credentials.passwordRepeat"
      />
    </div>
    <div>
      <button type="submit">Submit</button>
    </div>
  </form>
</template>

And lastly, for the index.vue component that replaces the App.vue as the home page add the following code.

<template>
  <h1>Welcome to the dashboard!</h1>
</template>

Ideally, the home page of the app is supposed to be guarded against unauthenticated access, meaning, all unauthenticated users ought to be redirected to the login page. To ensure this behavior, we will later create a guard that will take care of this.

Setting up the Supabase Nuxt 3 module

Next, integrate Supabase within the Nuxt 3 project.

Start by running the following script to install the Supabase module.

npm install -D @nuxtjs/supabase

Add the Supabase module to the Nuxt project's configuration - nuxt.config.js.

export default defineNuxtConfig({
  modules: ['@nuxtjs/supabase'],
});

The Supabase Nuxt module gives us access to the useSupabaseAuthClient, useSupabaseClient, and useSupabaseUser client-side Vue composables which can be utilized to achieve the goal within this tutorial.

Signing into the Nuxt 3 app

Inside the login.vue page, add a login function which with the assistance of the useSupabaseAuthClient() composable will set up the user sign-in logic.

// pages/login.vue

const client = useSupabaseAuthClient();
const router = useRouter();
const user = useSupabaseUser();

async function login() {
  const { email, password } = credentials;
  const { error } = await client.auth.signInWithPassword({ email, password });
  if (!error) return router.push('/');
  console.log(error);
}

watchEffect(async () => {
  if (user.value) {
    await router.push('/');
  }
});

Whenever issues arise with requests involved with the useSupabaseAuthClient()'s functions an error Object is always returned, else it is set to null.

In the above function when users are successfully authenticated the router is used to redirect them to the home page.

watchEffect is used in the above code to check if the user object is supplied by the useSupabaseUser() composable. This composable helps auto-import the authenticated Supabase user everywhere inside the Nuxt app. If the user is available as a result of a successful sign-in the app will redirect to the home page - /.

Registering users into the Nuxt 3 app

Inside the register.vue page, add a register() function that submits a registering user's details as filled in the provided form. The registration function's code is as follows.

// pages/register.vue

const client = useSupabaseAuthClient();

async function register() {
  const { firstName, surname, email, password, passwordRepeat } = credentials;
  if (password !== passwordRepeat) return;
  const { error } = await client.auth.signUp({
    email,
    password,
    options: {
      data: {
        first_name: firstName,
        surname,
        email,
      },
      emailRedirectTo: 'http://localhost:3000/login',
    },
  });
  if (error) {
    alert('Something went wrong !');
    return;
  }
  alert('Open the email we sent you to verify your account!');
}

As demonstrated previously with the signInWithPassword() function, the useSupabaseAuthClient() composable also gives access to a signUp function that takes some arguments the most important being the registering user's email and password. In this example, an options object is also passed which enables the specification of the redirection URL - emailRedirectTo sent in the account verification email received by the registering user. The data option specifies the extra information about the registering user being submitted along with the authentication credentials, in this case, the firstName and surname.

Up to this point, the app is not functional since the Supabase project credentials needed by the Nuxt module have not yet been provided, this will be done after having created a Supabase project.

Keeping unauthenticated users out

Before creating a Supabase project, an important piece of this app that was discussed above was the guarding of the home page from unauthorized access. This can be fulfilled by using a route middleware. Set one up by creating a /middleware root folder and adding a auth.js file in it. Add the following code inside this file.

// middleware/auth.js
export default defineNuxtRouteMiddleware((to, _from) => {
  const user = userSupabaseUser();

  if (!user.value) {
    return navigateTo('/login');
  }
});

The above route middleware ensures that unauthorized sessions to pages it's applied to are redirected to the /login page.

To apply the middleware to a page, in this case, the home page the following code needs to be added.

// pages/index.vue

definePageMeta({
  middleware: 'auth',
});

Logging users out

To log users out of the Nuxt app the logOut() function of the useSupabaseAuthClient() composable needs to be called.

Create a /components root directory and in it add a Navbar.vue component with the following code.

// components/Navbar.vue

<script setup>
  const client = useSupabaseAuthClient();
  const user = useSupabaseUser();
  const router = useRouter();

  async function logOut() {
    const { error } = await client.auth.signOut();
    if (error) return;
    await router.push('/login');
  }
</script>

<template>
  <nav>
    <div>
      <button v-if="user" @click="logOut()">Log Out</button>
    </div>
    <div>
      <NuxtLink v-if="!user" to="/login">Login</NuxtLink>
    </div>
    <div>
      <NuxtLink v-if="!user" to="/register">Register</NuxtLink>
    </div>
  </nav>
</template>

In this component the /login and /register routes are exposed when a user is not authenticated, else the Log Out button is displayed.

The Log Out button calls the logOut() function that logs a user out and redirects the app to the /login page.

To bring all the parts of the UI together, create a default layout component by adding a /layouts root folder and adding a default.vue component adding the following code in it.

<template>
  <Navbar></Navbar>
  <slot></slot>
</template>

Now, the navigation bar together with its routes and buttons will be visible on every page depending on the authentication status of the app.

Setting up Supabase for a Nuxt 3 app

The second part of this tutorial involves creating a Supabase project. To accomplish this, first, create a Supabase account.

After creating an account and completing the authentication process you'll end up in the Supabase dashboard. Create a new organization, then a new project.

image

After the project has been created, the proceeding page presents its details, amongst them its URL and key: anon public.

Copy project key and url

Create a .env file at the root of the Nuxt app, declaring a SUPABASE_URL and SUPABASE_KEY environmental variables in it, then populate them with the values obtained after the creation of the new Supabase project.

Setting up authentication within Supabase

Supabase provides a plethora of options to choose from when it comes to setting up user authentication. Users can be authenticated into an application by using various providers supported by Supabase's authentication as displayed in the figure below.

Supported authentication providers in Supabase

The above choice of providers can be viewed by going to Authentication > Providers within the Supabase dashboard.

By default, the email provider is enabled just as displayed in the above figure. The focus of this tutorial is on authenticating users into the Nuxt app created using the email method, so no changes are to be made here.

Supabase authentication credentials table

Supabase separates the authentication table from other data tables. On the event of registration of a user, the authentication credentials are placed inside the users table within the authentication section of the dashboard. Since no user has been registered, this table should be empty as demonstrated below.

Authentication users table

Within the Supabase dashboard, tables and other elements can be created graphically or using SQL scripts inside the SQL editor.

Apart from the user authentication credentials that will be stored inside the users table in this demonstrational app, additional user data such as the first and surnames of registered users will need to be stored somewhere else. To do this, a table needs to be created that will hold this extra data.

Paste and run the following SQL script inside the SQL editor to create some tables, functions, and triggers.

create table profiles (
  id uuid references auth.users on delete cascade not null primary key,
  updated_at timestamp with time zone,
  email text,
  first_name text,
  surname text
);
create function public.handle_new_registration()
returns trigger as $$
begin
  insert into public.profiles (id, email, first_name, surname)
  values (new.id, new.raw_user_meta_data->>'email', new.raw_user_meta_data->>'first_name', new.raw_user_meta_data->>'surname');
  return new;
end;
$$ language plpgsql security definer;
create trigger on_new_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_registration();

A profiles table that will hold the user data discussed previously is created in the first part of the above SQL script. From the create function to $$ language plpgsql; part a Postgres function that creates a new record inside the profiles table whenever a new user is registered written in plpgsql is created. Lastly, a trigger associated with the authentication - users table that calls the function above whenever a new record is added is also created.

The data passed as raw_user_meta_data inside the database function is obtained from what was passed inside the data option in the registration function created in the registration page - pages/register.vue.

Now, when a new user is registered, a new profiles table record will be created containing the fist_name, surname, and the email of the user as provided inside the registration form.

Putting all this together, the app should work as follows.

Complete app demonstration

Summary

To summarize this tutorial, we have learned the following:

  • Using route middlewares to guard pages in Nuxt 3
  • Adding modules to Nuxt 3 apps
  • Setting up and using the Supabase Nuxt 3 module
  • Using the composables provided by the Supabase module to authenticate users in and out of a Nuxt 3 app
  • The creation of a Supabase project and integration into a Nuxt 3 app

To learn more about Nuxt 3 and Supabase, you can refer to their respective documentation: