Home  >  Article  >  Web Front-end  >  Setting up Supabase Auth with Nuxt v3

Setting up Supabase Auth with Nuxt v3

Barbara Streisand
Barbara StreisandOriginal
2024-10-17 08:24:03685browse

Implementing authentication is something that you do on most projects, but still something that you may not remember how to do by memory because of how often you actually do it.

Here is a quick how-to about implementing Supabase Auth with Nuxt v3. In this example, we will be using OTP, but it applies to every case.

You will first want to start your project by going to Supabase's website.

After creating a project in Supabase and starting your project on Nuxt, we want to then install the Supabase Nuxt package by doing:

npx nuxi@latest module add supabase

We will then create our .env file and add the following environment variables:

SUPABASE_URL=<your_supabase_url>
SUPABASE_KEY=<your_supabase_key>

You can find these on the Supabase dashboard for your project, under Settings -> API

Setting up Supabase Auth with Nuxt v3

Afterwards, we can being setting up our project. I have made 2 very basic files so far:

  1. auth.ts (I used a Pinia store, but feel free to use just a regular file)
import { defineStore } from "pinia";

export const useAuthStore = defineStore("auth", () => {
  const supabase = useSupabaseClient();

  const sendOtp = async (email: string) => {
    const { error } = await supabase.auth.signInWithOtp({
      email,
    });

    if (error) {
      throw error;
    }

    return true;
  };

  const verifyOtp = async (email: string, otp: string) => {
    const { error } = await supabase.auth.verifyOtp({
      type: "email",
      token: otp,
      email,
    });

    if (error) {
      throw error;
    }

    return true;
  };

  return {
    sendOtp,
    verifyOtp,
  };
});
  1. LoginForm.vue
<template>
  <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md">
    <h2 class="text-3xl font-bold mb-6 text-center text-gray-800">Welcome</h2>
    <form @submit.prevent="handleSubmit" class="space-y-6">
      <div v-if="mode === 'email'">
        <label for="email" class="block mb-2 font-medium text-gray-700"
          >Email</label
        >
        <input
          type="email"
          id="email"
          v-model="email"
          required
          placeholder="Enter your email"
          class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
        />
      </div>

      <div v-else-if="mode === 'code'">
        <p class="mb-2 font-medium text-gray-700">
          Enter the 6-digit code sent to {{ email }}
        </p>
        <input
          type="text"
          v-model="otpCode"
          required
          placeholder="Enter 6-digit code"
          maxlength="6"
          class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
        />
      </div>

      <UButton
        icon="i-heroicons-paper-airplane"
        size="lg"
        color="primary"
        variant="solid"
        :label="buttonLabel"
        :trailing="true"
        block
      />
    </form>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import { useAuthStore } from "~/stores/auth";

const authStore = useAuthStore();

const email = ref("");
const otpCode = ref("");
const mode = ref("email");

const buttonLabel = computed(() => {
  return mode.value === "email" ? "Send One-Time Password" : "Verify Code";
});

const handleSubmit = async () => {
  if (mode.value === "email") {
    try {
      await authStore.sendOtp(email.value);
      mode.value = "code";
    } catch (error) {
      console.log("Error sending OTP: ", error);
    }
  } else {
    try {
      await authStore.verifyOtp(email.value, otpCode.value);
    } catch (error) {
      console.log("Error verifying OTP: ", error);
    }
  }
};
</script>

<style scoped></style>

Note that I am also using NuxtUI, in case you get any errors.

Because by default, the signInWithOtp function sends a magic link, you will have to change the email template on Supabase's dashboard to send a token:

Setting up Supabase Auth with Nuxt v3
This is found under Authentication -> Email Templates -> Change Confirm Signup and Magic Link templates to use {{ .Token }}

And that is pretty much it, you have a working auth!
If you want to add signout, you can also add a method to your previous file like such:

const signOut = async () => {
  const { error } = await supabase.auth.signOut();

  if (error) {
    throw error;
  }

  return true;
};

However, if you want to protect certain routes, we can also do that adding middleware.

Create a folder on the root called middleware (name is key) and a file called auth.ts.

You can then add something like this:

export default defineNuxtRouteMiddleware((to) => {
  const user = useSupabaseUser();

  const protectedRoutes = ["/app"];

  if (!user.value && protectedRoutes.includes(to.path)) {
    return navigateTo("/auth");
  }

  if (user.value && to.path === "/auth") {
    return navigateTo("/");
  }
});

This will basically protect from the server your /app route, so if you attempt to go to /app without being signed in, you will be redirected to /auth.

Likewise, if you try to visit /auth while already being signed in, you will be redirected to the home page /.

Now, to use this, you can place it inside the