使用Supabase在Vue.js中进行认证的终极指南

1,732 阅读8分钟

简介

认证是当今网络应用的一个重要功能,但许多开发人员在设置它时遇到了困难。值得庆幸的是,现在有一些服务和库可以帮助我们卸下这个沉重的负担。

今天我们将讨论如何使用Supabase来处理Vue.js应用程序中的用户认证。Supabase将作为应用程序中认证的后端,具有登录和注册功能,同时还有一个只能用有效凭证访问的私人路线。

什么是Supabase?

Supabase通常被描述为Firebase的一个开源替代品。它提供了Firebase的一些关键功能,其中之一包括用户认证和管理。

Supabase提供了对不同的外部认证提供者的支持,如密码、电话号码,以及谷歌、Twitter、Facebook和Github等身份供应商。

设置Vue.js

为了开始工作,我们将使用Vue CLI来快速搭建一个新项目的支架。CLI可以通过运行以下命令进行全局安装。

npm install -g @vue/cli
# OR
yarn global add @vue/cli

接下来,运行下面的命令来创建一个Vue项目。

vue create supabase-auth

你会被提示选择一个预设;选择手动选择功能的选项。
之后,选择RouterVuex并点击回车,然后选择Vue 3.x版本,因为我们将使用新的组合API。最后,在所有其他选择上点击回车,让你的Vue应用准备好。

设置Supabase

要想开始,首先你要通过访问Supabase的登录页面创建一个账户,并继续使用你的Github账户进行登录。

登录到仪表板后,点击新项目按钮,创建你的第一个项目。你应该看到弹出以下模式。

Screenshot of supabase create new project screen

为你的项目选择一个名称,一个数据库密码,以及一个离你很近的地区。该项目需要一些时间才能完全创建。

完成后,进入**设置,**然后是API,并复制URL和匿名公共API密钥。

Screenshot of Supabase config screen

在你的项目根部创建一个.env.local 文件,并将凭证保存在其中,就这样。

VUE_APP_SUPABASE_URL=YOUR_SUPABSE_URL
VUE_APP_SUPABASE_PUBLIC_KEY=YOUR_SUPABSE_PUBLIC_KEY

设置Supabase客户端库

运行下面的命令来安装Supabase客户端库。

yarn add @supabase/supabase-js

接下来我们要初始化Supabase,在我们的src 目录中创建一个supabase.js 文件并粘贴以下代码。

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

 const supabaseUrl = process.env.VUE_APP_SUPABASE_URL
 const supabaseAnonKey = process.env.VUE_APP_SUPABASE_KEY

 export const supabase = createClient(supabaseUrl, supabaseAnonKey)

创建我们的页面

现在让我们创建一些Vue组件页面来处理我们项目的注册和登录功能,以及一个仪表盘页面。

在本教程中,我们将不对我们的应用程序进行样式设计,以避免混淆我们的HTML标记,但你总是可以选择你喜欢的样式。

这里是我们的SignIn 页面的标记。

<!-- src/views/SignIn.vue -->
 <template>
  <div>
      <h1>Login Page</h1>
      <form @submit.prevent="signIn">
        <input
          class="inputField"
          type="email"
          placeholder="Your email"
          v-model="form.email"
        />
        <input
          class="inputField"
          type="password"
          placeholder="Your Password"
          v-model="form.password"
        />
        <button type="submit">Sign In</button>
      </form>
      <p>
        Don't have an account? 
        <router-link to="/sign-up">Sign Up</router-link>
      </p>
    </div>
</template>

现在让我们为我们的SignUp 页面做标记。

<!-- src/views/SignUp.vue -->
<template>
  <div>
      <h1>SignUp Page</h1>
      <form @submit.prevent="signUp">
        <input
          class="inputField"
          type="email"
          placeholder="Your email"
          v-model="form.email"
        />
        <input
          class="inputField"
          type="password"
          placeholder="Your Password"
          v-model="form.password"
        />
        <button type="submit">Sign Up</button>
      </form>
      <p>
        Already have an account? 
        <router-link to="/sign-in">Log in</router-link>
      </p>
    </div>
</template>

最后,我们的Dashboard 页面。

<!-- src/views/Dashboard.vue -->
<template>
  <div>
    <h1>Welcome to our Dashboard Page</h1>
    <button @click.prevent="signOut">Sign out</button>
    <p>Welcome: {{ userEmail }}</p>
  </div>
</template>

用Vue Router设置路由

现在我们已经创建了我们的页面,我们需要设置路由,以便我们可以在它们之间移动。为此,我们将使用Vue Router。

让我们在路由器文件中为我们的不同页面声明路由,就像这样。

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
function loadPage(view) {
  return () =>
    import(
      /* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`
    );
}
const routes = [
  {
    path: '/',
    name: 'Dashboard',
    component: loadPage("Dashboard"),
    meta: {
      requiresAuth: true,
    }
  },
  {
    path: '/sign-up',
    name: 'SignUp',
    component: loadPage("SignUp")
  },
  {
    path: '/sign-in',
    name: 'SignIn',
    component: loadPage("SignIn")
  },
]
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach((to, from, next) => {
  // get current user info
  const currentUser = supabase.auth.user();
  const requiresAuth = to.matched.some
  (record => record.meta.requiresAuth);

  if(requiresAuth && !currentUser) next('sign-in');
  else if(!requiresAuth && currentUser) next("/");
  else next();
})

export default router

我们第一个路由中的meta 对象是用来保存该路由的额外信息的。它有一个名为requiresAuth 的属性,这个属性被设置为true ,我们要用这个属性来防范这个路由的未经认证的用户。

从第34-42行,我们正在设置所谓的 "导航卫士"。

代码中发生的事情是进行检查,以确定某个路由是否需要认证,以及是否有用户当前正在登录。如果该路线需要认证,但没有人登录,用户将被重定向到sign-in 。但如果该路由需要认证,并且有一个用户登录,那么用户就会被重定向到仪表板的私人路由。

设置Vuex

Vuex是Vue应用程序中的一个工具,用于存储我们应用程序中所有组件可访问的数据。它有自己的一套规则,确保存储的数据可以被相应地改变和更新。

我们将在Vuex中存储我们组件的所有逻辑。

使用Vuex的一个注意事项是,一旦页面被重新加载,所有存储的数据都会被重置。为了解决这个问题,我们将使用vuex-persistedstate。这个包可以帮助保存存储在Vuex中的数据,即使在页面重新加载之后。

在你的终端输入以下内容来安装vuex-persistedstate。

yarn add vuex-persistedstate
#OR
npm install --save vuex-persistedstate

配置我们的Vuex商店

这里,我们正在配置vuex-persistedstate ,然后导入Supabase和Vue Router。我们将需要它们来创建我们的Vuex商店动作。

import { createStore } from 'vuex'
import createPersistedState from "vuex-persistedstate";
import router from '../router';
import { supabase } from "../supabase";

// Create store
export default createStore({
  state:{},
  mutations:{},
  actions:{},
  plugins: [createPersistedState()]
});

将数据存储在State

我们的Vuex商店中的state 对象是实际存储数据的。在这里我们可以定义我们的数据的默认值。

state: {
  user:null
};

在我们的state 对象中,我们将user 的默认值设置为null ,因为这是在用户没有登录到我们的应用程序时的值。

用突变来改变状态

突变是我们改变Vuex存储中state 对象的唯一方法。

一个突变接受state 和一个来自提交行动的值,就像这样。

  mutations: {
    setUser(state, payload) {
      state.user = payload;
    },
  },

当这个突变被提交时,它将我们的user 状态的默认值改为传递给它的任何值。

使用Actions 来提交突变

actions 对象包含了可以用来提交突变的函数,以改变我们应用程序中的状态。动作也可以调度其他动作。对于我们的示例应用程序,我们将使用三个不同的动作:注册、登录和退出。

签到动作

我们的signUpAction 动作接收表单数据,然后调用Supabase的注册功能。这个函数接收收集到的表单数据,对其进行验证,并在所有要求都满足的情况下创建一个新用户。

  async signUpAction({dispatch}, form) {
      try {
        const { error } = await supabase.auth.signUp({
          email: form.email,
          password: form.password,
        });
        if (error) throw error;
        alert("You've been registered successfully");
        await dispatch("signInAction", form)
      } catch (error) {
        alert(error.error_description || error.message);
      }
    },

一旦用户被创建,就会弹出一个带有成功信息的警告,然后派发signInAction 动作。singInAction 接受我们的表单数据,并记录我们新注册的用户,这样他们就可以访问私人仪表板路线。如果在任何时候失败,就会弹出一个错误警报。

登录动作

signInAction 动作也接收了用户填写的表单数据。它将这些数据传递给我们的SupabasesignIn 函数,该函数根据我们的用户表验证这些数据,以检查这些用户是否存在。如果是的话,用户就会被登录并被重定向到私人仪表板路线。

接下来,我们提交setUser 突变,它将我们的user 状态的值设置为当前登录的用户的电子邮件。

  async signInAction({ commit }, form) {
    try {
      const { error, user } = await supabase.auth.signIn({
        email: form.email,
        password: form.password,
      });
      if (error) throw error;
      alert("You've Signed In successfully");
      await router.push('/')
      commit('setUser', user.email)
    } catch (error) {
      alert(error.error_description || error.message);
    }
  },

签出动作

我们的signOutAction 动作调用了SupabasesignOut 函数,将我们的user 状态的值重设为空,然后将用户重定向到登录页面。

  async signOutAction({ commit }) {
    try {
      const { error } = await supabase.auth.signOut();
      if (error) throw error;
      commit('setUser', null)
      alert("You've been logged Out successfully");
      await router.push("/sign-in");
    } catch (error) {
      alert(error.error_description || error.message);
    }
  },

最后,这就是你的Vuex商店应该有的样子。

// src/store/index.js
import { createStore } from 'vuex'
import createPersistedState from "vuex-persistedstate";
import router from '../router';
import { supabase } from "../supabase";
export default createStore({
  state: {
    user: null,
  },
  mutations: {
    setUser(state, payload) {
      state.user = payload;
    },
  },
  actions: {
    async signInAction({ commit }, form) {
      try {
        const { error, user } = await supabase.auth.signIn({
          email: form.email,
          password: form.password,
        });
        if (error) throw error;
        alert("You've Signed In successfully");
        await router.push('/')
        commit('setUser', user.email)
      } catch (error) {
        alert(error.error_description || error.message);
      }
    },
    async signUpAction({dispatch}, form) {
      try {
        const { error} = await supabase.auth.signUp({
          email: form.email,
          password: form.password,
        });
        if (error) throw error;
        alert("You've been registered successfully");
        await dispatch("signInAction", form)
      } catch (error) {
        alert(error.error_description || error.message);
      }
    },
    async signOutAction({ commit }) {
      try {
        const { error } = await supabase.auth.signOut();
        if (error) throw error;
        commit('setUser', null)
        alert("You've been logged Out successfully");
        await router.push("/sign-in");
      } catch (error) {
        alert(error.error_description || error.message);
      }
    },
  },
  modules: {
  },
  plugins: [createPersistedState()],
})

为组件添加逻辑

现在是时候让我们倒退一下,通过添加一些逻辑使我们之前创建的组件完全发挥作用。

让我们从我们的SignUp 组件开始。

<!-- src/views/SignUp.vue -->
<template>
  <div>
   <!-- Our markup goes here -->
  </div>
</template>
<script>
import { reactive } from "vue";
import { useStore } from "vuex";
export default {
  setup() {
    // wrap data gotten from form input in vue's reactive object
    const form = reactive({
      email: "",
      password: "",
    });
    //create new store instance
    const store = useStore();
    const signUp = () => {
      // dispatch the signup action to register new user
      store.dispatch("signUpAction", form);
    };
    return {
      form,
      signUp,
    };
  },
};
</script>

现在,让我们为我们的SignIn 组件添加逻辑。SignInSignUp 组件是相似的;唯一的区别是调用signIn 函数而不是signUp 函数。

<!-- src/views/SignIn.vue -->
<template>
  <div>
   <!-- Our markup goes here -->
  </div>
</template>
<script>
import { reactive } from "vue";
import { useStore } from "vuex";
export default {
  setup() {
    // wrap data gotten from form input in vue's reactive object
    const form = reactive({
      email: "",
      password: "",
    });
    //create new store instance
    const store = useStore();
    const signUp = () => {
      // dispatch the sign in action to Log in the user
      store.dispatch("signInAction", form);
    };
    return {
      form,
      signIn,
    };
  },
};
</script>

让我们也给Dashboard 组件添加逻辑,这样我们的登录用户就可以在他们想登录的时候退出。

<!-- src/views/Dashboard.vue -->
<template>
  <div>
    <h1>Welcome to our Dashboard Page</h1>
    <button @click.prevent="signOut">Sign out</button>
    <p>Welocome: {{ userEmail }}</p>
  </div>
</template>
<script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    //create store instance
    const store = useStore();
    // Fetches email of logged in user from state
    const userEmail = computed(() => store.state.user);
     const signOut = () => {
      // dispatch the sign out action to log user out
      store.dispatch("signOutAction");
    };
    return {
      signOut,
      userEmail,
    };
  },
};
</script>

这样就完成了我们需要的所有逻辑,使我们的组件开始运行。

总结

在本教程中,我们回顾了如何使用Supabase和Vue进行用户认证。我们还学习了如何在我们的Vue应用中使用Vuex和Vue Router以及新的组合API。

如果你想打好基础,本教程的完整源代码可以在这里找到。

The post Theultimate guide to authentication in Vue.js with Supabaseappeared first onLogRocket Blog.