introduction
Supabase is a self-proclaimed "open source Firebase alternative". I've been working with Supbase for a while and I think I'll try to use their authentication API to authenticate setup for 3 applications.
First, why do you want to use Supabase Auth? Most importantly, if you use Supabase as your data store,(It has some very sweet benefits), Supabase Auth is the only way you can manage access to this data. Secondly, although Supabase Auth also has many different features.
- No middleware user permissions (row-level security via Postgres).
- Magical email link
- Login for social providers
- And all the other Auth features you'd expect to be encapsulated in a simple JavaScript SDK
To sum up, it is worth mentioning that Supabase Auth is not an independent certification provider, it is only for use with other Supabase services (data, storage, etc.).
OK, let's start implementing Supabase Auth in our 3 apps.
Install Supabase
I'm assuming you already have a 3 application set up with Vue Router, if not, you can download itThis template codeto perform operations. I also assume you already have a Supabase account and project settings. If not, you can go, it will guide you through the first step.
Then, in order to work with Supabase auth and any other services on it, we need to install the Supabase JavaScript SDK.
npm install @supabase/supabase-js
Set up Supabase
After installing Supabase, we need to go through several steps to set it up. I'll create a name calledThe combination of , is used to organize this setting.
// import { createClient } from "@supabase/supabase-js"; // these can come from an environment variable if desired // not required however as they are 100% exposed on the client side anyway // and that's ok, Supabase expects this (security is provided by Row Level Security) const supabaseUrl = ""; const supabaseKey = ""; // setup client const supabase = createClient(supabaseUrl, supabaseKey); // expose supabase client export default function useSupabase() { return { supabase }; }
supabaseUrl and supabaseKey can be used on the Supabase dashboardSettings > API
。
When you are in the Supabase interface, you can also goAutentication > Settings
, set your website URL so that Supabase knows how to correctly redirect confirmation emails, etc. Its default value is localhost:3000, but you can modify it as needed.
Create an AuthUser combination
Once the Supabase SDK is set up, we can now start using it. First, I will create an AuthUser combination to abstract some interaction with AuthUser and retain all the methods we need to fill in. This is helpful when we want to get out of Supabase in the future and helps us to bring all the AuthUser features into one place.
import { ref } from "vue"; // user is set outside of the useAuthUser function // so that it will act as global state and always refer to a single user const user = ref(null); export default function useAuthUser() { /** * Login with email and password */ const login = async ({ email, password }) => {}; /** * Login with google, github, etc */ const loginWithSocialProvider = (provider) => {}; /** * Logout */ const logout = async () => {}; /** * Check if the user is logged in or not */ const isLoggedIn = () => {}; /** * Register */ const register = async ({ email, password, ...meta }) => {}; /** * Update user email, password, or meta data */ const update = async (data) => {}; /** * Send user an email to reset their password * (ie. support "Forgot Password?") */ const sendPasswordRestEmail = async (email) => {}; return { user, login, loginWithSocialProvider, isLoggedIn, logout, register, update, sendPasswordRestEmail, maybeHandleEmailConfirmation, }; }
Create a page
Next, let's create the pages required for a typical user authentication process. I've done some style designs using Tailwind CSS, but I can freely adjust these styles according to your needs.
Register.vue
The first page we need is a registration page. It needs to include the necessary fields to create a user in Supabase. These fields are email and password. We can also add arbitrary metadata for our users in Supabase, which means we can also make the user's real name part of the registration.
// <script>...</script> <template> <form class="max-w-lg m-auto" @="handleSubmit"> <h1 class="text-3xl mb-5">Register</h1> <label>Name <input v-model="" type="text" /></label> <label>Email <input v-model="" type="email" /></label> <label>Password <input v-model="" type="password" /></label> <button>Register</button> </form> </template>
In the script section, we can use a reactive reference to keep up with the form's data while providing a function to process the form's submission. On form submission, we will call the registration function from the AuthUser combination (it is still empty, but we will populate it later with a concrete call from supabase) and then redirect to a page instructing the user to check their email to confirm registration.
// <script setup> import { ref } from "vue"; import useAuthUser from "@/composables/UseAuthUser"; import { useRouter } from "vue-router"; // Use necessary composables const router = useRouter(); const { register } = useAuthUser(); // Form reactive ref to keep up with the form data const form = ref({ name: "", email: "", password: "", }); // function to hand the form submit const handleSubmit = async () => { try { // use the register method from the AuthUser composable await register(); // and redirect to a EmailConfirmation page the will instruct // the user to confirm they're email address ({ name: "EmailConfirmation", query: { email: }, }); } catch (error) { alert(); } }; </script> <template>...</template>
Finally, we need to register a route for this page.
// router/ const routes = [ //... { name: "Register", path: "/register", component: () => import("@/pages/"), }, ]
If you use the template code, visit/register
, you should get a page that looks like this.
Since we redirect to an EmailConfirmation page after registration, we certainly need to make it exist. It will be just a simple template and a message. When we redirect above, we also provide an email from the registration form as a query variable, so we can display it here.
<template> <div> <h1 class="text-3xl">Thanks for registering!</h1> <p> Please confirm your email to finishing registering: {{ $ }} </p> </div> </template>
Once again, we also need to register this route.
// router/ const routes = [ //... { name: "EmailConfirmation", path: "/email-confirmation", component: () => import("@/pages/"), }, ]
Now access to the/email-confirmation
The route will look like this.
/[email protected]
Log in.vu
After registering and confirming their email, users will have to log in. Let's create this page next.
The template will include a form with email and password fields, as well as a link to the Forgot Password page, as well as a link to handle the Github login.
<script>...</script> <template> <div class="max-w-lg m-auto"> <form @="handleLogin"> <h1 class="text-3xl mb-5">Login</h1> <label>Email <input v-model="" type="email" /></label> <label>Password <input v-model="" type="password" /></label> <button>Login</button> <router-link to="/forgotPassword">Forgot Password?</router-link> </form> <div class="mt-5"> <a @="handleLogin('github')">Github</a> </div> </div> </template>
In the script section, we can create a reactive reference to keep up with the form's value and a function to handle the form's submission.
<script setup> import { ref } from "vue"; import useAuthUser from "@/composables/UseAuthUser"; import { useRouter } from "vue-router"; // Use necessary composables const router = useRouter(); const { login, loginWithSocialProvider } = useAuthUser(); // keep up with form data const form = ref({ email: "", password: "", }); // call the proper login method from the AuthUser composable // on the submit of the form const handleLogin = async (provider) => { try { provider ? await loginWithSocialProvider(provider) : await login(); ({ name: "Me" }); } catch (error) { alert(); } }; </script> <template>...</template>
After registering the route, you can access it/login
Routing, see a nice login form.
// router/ const routes = [ //... { name: "Login", path: "/login", component: () => import("@/pages/"), }, ]
Since we implemented a link to forget the password above, let's set this page with scaffolding as well.
In the template we just need a form with a single email field in order to collect emails we should send the reset link.
<script>...</script> <template> <form class="max-w-lg m-auto" @="handlePasswordReset()"> <h1 class="text-3xl mb-5">Forgot Password?</h1> <label>Email <input v-model="email" type="email" /></label> <button>Send Reset Email</button> </form> </template>
In the script section, we will create an email reactive reference to keep up with email input in the form and define a function that calls AuthUser to composable when the form is submittedsendPasswordRestEmail
function.
<script setup> import useAuthUser from "@/composables/UseAuthUser"; import { ref } from "vue"; // use necessary composables const { sendPasswordRestEmail } = useAuthUser(); // keep up with email const email = ref(""); // function to call on submit of the form // triggers sending the reset email to the user const handlePasswordReset = async () => { await sendPasswordRestEmail(); alert(`Password reset email sent to: ${}`); }; </script>
Finally, we will register this route.
// router/ const routes = [ //... { name: "ForgotPassword", path: "/forgotPassword", component: () => import("@/pages/"), }, ]
Now, clicking the "Forgot Password?" link on the login page will bring you here.
The last page we need is a profile page that displays their secret information after the user logs in. We call it/me
。
// router/ const routes = [ //... { name: "Me", path: "/me", component: () => import("@/pages/"), }, ]
We will also add routing middleware to let our program know that this should be a protected route that can only be accessed by authenticated users.
{ name: "Me", meta: { requiresAuth: true, }, //... },
Then, in order for this middleware to really work, we need to implement it like this.
const router = createRouter({ history: createWebHistory(), routes, }); ((to) => { // here we check it the user is logged in // if they aren't and the route requries auth we redirect to the login page const { isLoggedIn } = useAuthUser(); if (!isLoggedIn() && ) { return { name: "Login" }; } }); export default router;
The page itself will be a simple greeting message to the user.
<script setup> import useAuthUser from "@/composables/UseAuthUser"; const { user } = useAuthUser(); </script> <template> <div v-if="user"> <!--user_metadata is the key supabase nests all arbitrary meta data under--> <div>Hello {{ user.user_metadata.name }}</div> </div> </template>
We won't try to view this page yet, because we haven't implemented the login yet.
/danielkellyio/supabase-auth-example
Now that the page and AuthUser combination are in place, we can start populating the contents of the AuthUser combination function by calling the Supabase SDK.
The first thing we need to do is access our Supabase client. We can do this by importing the UseSupabase composite function and deconstructing the Supabase client from it.
import useSupabase from "@/composables/UseSupabase"; export default function useAuthUser() { const { supabase } = useSupabase(); //... }
Now we can fill them one by one through our empty functions.
login()
In order to log in to Supabase, we need to call. We can also wait for the response, throw a new error if there is an error, otherwise we will return to the logged-in user.
const login = async ({ email, password }) => { const { user, error } = await ({ email, password }); if (error) throw error; return user; };
loginWithSocialProvider()
loginWithSocialProvider is also simple. Just pass the provider to the signIn method.
const loginWithSocialProvider = async (token) => { const { user, error } = await ({ provider }); if (error) throw error; return user; };
logout()
Now, in order to log out, we need to call Supabase's signOut method. Since the user is no longer available, nothing can be returned from logout.
const logout = async () => { const { error } = await (); if (error) throw error; };
isLoggedIn()
For the isLoggedIn function, we just need to check if the reactive ref user has a value.
const isLoggedIn = () => { return !!; };
If you're thinking that we've never set this value when logging in to the user, you're completely correct, but we'll use another small supabase method to help us solve this problem, in just one minute.
register()
The register function looks almost the same as the login function, receiving emails and passwords. However, it also requires acceptance of other user information (i.e. metadata). Also, we will redirect to the profile page and attach some query variables that contain some useful information.
const register = async ({ email, password, ...meta }) => { const { user, error } = await ( { email, password }, { //arbitrary meta data is passed as the second argument under a data key // to the Supabase signUp method data: meta, // the to redirect to after the user confirms their email // wouldn't be available if we were rendering server side // but since we're all on the client it will work fine redirectTo: `${}/me?fromEmail=registrationConfirmation"`, } ); if (error) throw error; return user; };
Please note this cool little trick, let's put meta:...meta
. This allows us to provide the same level of metadata in the object passed to the function when calling the function, but access it separately in the function.
// for example register({email: '[email protected]', password: 'password123', name: 'Daniel Kelly', favoriteFood: 'Spaghetti'}) // meta will be {name: 'Daniel Kelly', favoriteFood: 'Spaghetti'}
update()
Although we don't actually provide an interface to update the user, we can implement this function because Supabase makes it so simple.
const update = async (data) => { const { user, error } = await (data); if (error) throw error; return user; };
sendPasswordResetEmail()
The last function to be implemented issendPasswordResetEmail
. Once again, there is a simple solution for supabase.
const sendPasswordRestEmail = async (email) => { const { user, error } = await (email); if (error) throw error; return user; };
Observe the changes in Auth state
At this point, we are almost ready to start using our interface, but there is a critical step to perform. We need to know when the user is logged in or out and update the reactive ref in the AuthUser combination accordingly.
Your first idea might be to do this in the login and logout method, which will work at some point. However, what if the user logs out because the session expires? Or what if the user is updated or deleted on Supabase? In both cases, our login and logout methods will not be called.
To solve this problem, Supabase provides aonAuthStateChange
function.
We can call this function in our supabase combination, let it listen for all changes in auth state, and then set our user reactive ref accordingly.
// import { createClient } from "@supabase/supabase-js"; import useAuthUser from "@/composables/UseAuthUser"; // config const supabaseUrl = ""; const supabaseKey = ""; // setup client const supabase = createClient(supabaseUrl, supabaseKey); // ⬇ setup auth state listener ⬇ ((event, session) => { // the "event" is a string indicating what trigger the state change (ie. SIGN_IN, SIGN_OUT, etc) // the session contains info about the current session most importanly the user dat const { user } = useAuthUser(); // if the user exists in the session we're logged in // and we can set our user reactive ref = session?.user || null; }); // expose supabase client export default function useSupabase() { return { supabase }; }
I chose to do this outside of the function call in so that it is called only once and organized with other Supabase setup code.
Test stuff
Now is the critical moment. We should have most of the work. (Although you'll see right away, we need to make some more adjustments). Navigate to the registration page in your browser and register.
After that, you should be successfully redirected to the EmailConfirmation page, and your email address is displayed in the message.
Also, if you check your inbox, you will receive emails as you expected.
By the way, if you want to customize the look of this email, you can do it on the Supabase dashboardAuthentication > Templates
。
Also, if you areAuthentication > Users
, you can see that the status of your newly registered user is:Waiting for Verication
!
Very good, now go and click on the link in the email. Oh, my goodness! We were redirected to a new website. We were redirected to the login page...this is not right. However, note that the link at the top right of the title does say "Log Out"
If we clickme
page, it will let us access it and correctly display the name we provide in the registration form.
The problem is that the moment we click on the page, our middleware is running and we are not fully logged in yet, so authStateChange has not happened yet and our user reflex has not been set yet.
Let's make an exception in our middleware, if the query variable fromEmail exists, we continue to let the navigation pass because we know that from the confirmation email, the user will log in immediately.
((to) => { const { isLoggedIn } = useAuthUser(); if ( !isLoggedIn() && && !().includes("fromEmail") ) { return { name: "Login" }; } });
Also note that this is not a security issue. If someone is not logged in, please include it in the query stringfromEmail
, Since the user does not exist, nothing will be displayed anyway, and information about the user will not be received from Supabase.
Log out
Now everything should be fine except for the logout link. We can make it work by defining a logout route and adding a route protection directly to it.
{ name: "Logout", path: "/logout", beforeEnter: async () => { const { logout } = useAuthUser(); await logout(); return { name: "Home" }; }, },
After logging out, if you prefer, you can try signing up again with a different email to confirm that our fix above works for direct navigation to the profile page. Similarly, when logging out, check the login page. You should also be able to log in successfully with your existing users!
Homework
Finally, if you use the link to forget your password, Supabase does send you an email to reset your password, however you still need to implement the form. I dare say you can use what you learned in the article to study this problem and use AuthUser to composeupdate
method.
Summarize
Supabase is a new and upcoming alternative to Firebase. It has many attractive features, such as open source, using PostgreSQL as a database, and providing popular services such as data storage, authentication and file storage. It is fairly simple to authenticate your 3 applications with their certification services because their certification API is simple.
If you want to see the full code discussed in this article, you can check it outGithub repo。
Once the authentication is complete, you can start making requests to your Supabase instance and create rules to limit which users can access which data.
So go and make the real Supabase app.
/courses/vuejs-3-fundamentals?ref=blog
The above is the detailed explanation of the Supabase Auth method in Vue3. For more information about using Supabase Auth in Vue3, please follow my other related articles!