发布于 2026-01-05 7 阅读
0

Nuxt 3 中的身份验证

Nuxt 3 中的身份验证

如何在 Nuxt 3 中添加身份验证

我看过一些关于这个主题的教程,但大多数教程都涵盖了使用 Supabase、Amplify 或 Firebase 进行身份验证,这些服务大多都有一个 Nuxt 组件,可以更轻松地为您的网站添加身份验证。

如果你和我一样,使用中间件通过调用提供令牌的端点来处理应用程序的身份验证状态,那么我将向你展示如何在 Nuxt 3 中实现这一点。

我将使用DummyJSON 的模拟 API 来帮助我完成这项工作。

什么是 DummyJSON?

使用 DummyJSON,您可以获得不同类型的 REST 端点,其中填充了 JSON 数据,您可以在使用自己喜欢的框架和库开发前端时使用这些端点,而无需担心编写后端。

本质上,它是一个模拟 API,只有几个端点,最重要的是它提供了一个 REST 登录端点,该端点会返回一个伪造的令牌。

创建项目

首先,让我们从创建一个项目开始。

npx nuxi init nuxt3-auth

在项目根目录下创建以下文件夹/文件

  • 页面

    • index.vue
    • 登录.vue
    • 关于.vue
  • 布局

    • default.vue

删除app.vue

创建所需页面

pages/index.vue

<template>
  <div>Hello Home Page</div>
</template>
<script lang="ts" setup></script>
Enter fullscreen mode Exit fullscreen mode

pages/about.vue

<template>
  <div>About Page</div>
</template>
<script lang="ts" setup></script>
Enter fullscreen mode Exit fullscreen mode

登录页面会非常简洁,只有用户名和密码输入框以及一个登录按钮。

pages/login.vue

<template>
  <div>
    <div class="title">
      <h2>Login</h2>
    </div>
    <div class="container form">
      <label for="uname"><b>Username</b></label>
      <input
        v-model="user.username"
        type="text"
        class="input"
        placeholder="Enter Username"
        name="uname"
        required
      />

      <label for="psw"><b>Password</b></label>
      <input
        v-model="user.password"
        type="password"
        class="input"
        placeholder="Enter Password"
        name="psw"
        required
      />

      <button @click.prevent="login" class="button">Login</button>
    </div>
  </div>
</template>
<script lang="ts" setup>
const user = ref({
  username: '',
  password: '',
});

const login = async () => {
  // TODO send user Data to the login endpoint and redirect if  successful 
};
</script>
Enter fullscreen mode Exit fullscreen mode

创建我们的默认布局

Nuxt 提供了一个可自定义的布局框架,您可以在整个应用程序中使用它,非常适合将常见的 UI 或代码模式提取为可重用的布局组件。
布局文件放置在layouts/指定目录中,并在使用时通过异步导入自动加载。

我们的布局将包含一个导航栏,其中包含“首页”、“关于我们”和“登录”链接,底部是一个页脚,中间是我们的内容,这些内容<slot/>将自动替换为页面中的代码。

layouts/default.vue

<template>
  <div>
    <header>
      <ul>
        <li><nuxt-link to="/">Home</nuxt-link></li>
        <li><nuxt-link to="/about">About</nuxt-link></li>
        <li v-if="!authenticated" class="loginBtn" style="float: right">
          <nuxt-link to="/login">Login</nuxt-link>
        </li>
      </ul>
    </header>
    <div class="mainContent">
      <slot />
    </div>
    <footer>
      <h1>Footer</h1>
    </footer>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

中间件

Nuxt 提供了一个可定制的路由中间件框架,您可以在整个应用程序中使用它,非常适合提取在导航到特定路由之前需要运行的代码。
路由中间件是导航守卫,它接收当前路由和下一个路由作为参数。

我们将创建一个命名路由中间件,它位于指定middleware/目录中,当页面使用该中间件时,它将通过异步导入自动加载。

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
    console.log('From auth middleware')
})
Enter fullscreen mode Exit fullscreen mode

添加控制台日志以验证其在我们的主页上是否正常工作,现在我们可以添加此中间件了。

pages/index.vue

<template>
  <div>Hello Home Page</div>
</template>
<script lang="ts" setup>
  definePageMeta({
    middleware: 'auth' // this should match the name of the file inside the middleware directory 
})
</script>
Enter fullscreen mode Exit fullscreen mode

运行代码并检查验证是否可以看到console.log

在这种情况下,我想保护所有路由,那么如何使这个中间件全局生效呢?我们只需要.global像这样在文件中添加后缀auth.global.ts,它就会在每次路由更改时自动运行。

现在我们可以从首页移除这段代码了。

  definePageMeta({
    middleware: 'auth' // this should match the name of the file inside the middleware directory 
})
Enter fullscreen mode Exit fullscreen mode

与皮尼亚一起购物

我打算创建一个身份验证存储来处理登录和已验证状态。我已经写过一篇文章介绍如何在 Nuxt 3 中设置 Pinia。Pinia和 Nuxt 3

// store/auth.ts

import { defineStore } from 'pinia';

interface UserPayloadInterface {
  username: string;
  password: string;
}

export const useAuthStore = defineStore('auth', {
  state: () => ({
    authenticated: false,
    loading: false,
  }),
  actions: {
    async authenticateUser({ username, password }: UserPayloadInterface) {
      // useFetch from nuxt 3
      const { data, pending }: any = await useFetch('https://dummyjson.com/auth/login', {
        method: 'post',
        headers: { 'Content-Type': 'application/json' },
        body: {
          username,
          password,
        },
      });
      this.loading = pending;

      if (data.value) {
        const token = useCookie('token'); // useCookie new hook in nuxt 3
        token.value = data?.value?.token; // set token to cookie
        this.authenticated = true; // set authenticated  state value to true
      }
    },
    logUserOut() {
      const token = useCookie('token'); // useCookie new hook in nuxt 3
      this.authenticated = false; // set authenticated  state value to false
      token.value = null; // clear the token cookie
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

我们有两个行动authenticateUser方案,logUserOut

authenticateUser函数接收用户名和密码作为有效负载,然后我们使用useFetch钩子函数向dummyjson/auth/login中的端点发送 POST 请求,并在请求体中传递用户名和密码。 我们应该收到类似这样的响应。

{
  "id": 15,
  "username": "kminchelle",
  "email": "kminchelle@qq.com",
  "firstName": "Jeanne",
  "lastName": "Halvorson",
  "gender": "female",
  "image": "https://robohash.org/autquiaut.png?size=50x50&set=set1",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsInVzZXJuYW1lIjoia21pbmNoZWxsZSIsImVtYWlsIjoia21pbmNoZWxsZUBxcS5jb20iLCJmaXJzdE5hbWUiOiJKZWFubmUiLCJsYXN0TmFtZSI6IkhhbHZvcnNvbiIsImdlbmRlciI6ImZlbWFsZSIsImltYWdlIjoiaHR0cHM6Ly9yb2JvaGFzaC5vcmcvYXV0cXVpYXV0LnBuZz9zaXplPTUweDUwJnNldD1zZXQxIiwiaWF0IjoxNjM1NzczOTYyLCJleHAiOjE2MzU3Nzc1NjJ9.n9PQX8w8ocKo0dMCw3g8bKhjB8Wo7f7IONFBDqfxKhs"
}
Enter fullscreen mode Exit fullscreen mode

如果有数据,我们会将令牌保存到 cookie 中。

logUserOut此函数只是简单地从 cookie 中删除令牌。

最终确定

现在我们需要修改中间件、登录和布局。

登录页面

现在我们可以导入身份验证存储并完成登录功能。

pages/login.vue


<script lang="ts" setup>
import { storeToRefs } from 'pinia'; // import storeToRefs helper hook from pinia
import { useAuthStore } from '~/store/auth'; // import the auth store we just created

const { authenticateUser } = useAuthStore(); // use authenticateUser action from  auth store

const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive with storeToRefs

const user = ref({
  username: 'kminchelle', 
  password: '0lelplR',
});
const router = useRouter();

const login = async () => {
  await authenticateUser(user.value); // call authenticateUser and pass the user object
  // redirect to homepage if user is authenticated
  if (authenticated) {
    router.push('/');
  }
};
</script>
Enter fullscreen mode Exit fullscreen mode

布局

在默认布局中,我们会根据应用程序的状态显示登录/注销按钮,并处理注销事件。

authenticated根据状态调整导航栏以显示或隐藏按钮

        <li v-if="!authenticated" class="loginBtn" style="float: right">
          <nuxt-link to="/login">Login</nuxt-link>
        </li>
        <li v-if="authenticated" class="loginBtn" style="float: right">
          <nuxt-link @click="logout">Logout</nuxt-link>
        </li>
Enter fullscreen mode Exit fullscreen mode

将以下内容添加到脚本中layouts/default.vue

<script lang="ts" setup>
import { storeToRefs } from 'pinia'; // import storeToRefs helper hook from pinia
import { useAuthStore } from '~/store/auth'; // import the auth store we just created

const router = useRouter();


const { logUserOut } = useAuthStore(); // use authenticateUser action from  auth store
const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive with storeToRefs

const logout = () => {
  logUserOut();
  router.push('/login');
};
</script>
Enter fullscreen mode Exit fullscreen mode

中间件

现在,在中间件中,我们可以根据 cookie 中令牌的值来处理身份验证。

export default defineNuxtRouteMiddleware((to) => {
  const { authenticated } = storeToRefs(useAuthStore()); // make authenticated state reactive
  const token = useCookie('token'); // get token from cookies

  if (token.value) {
    // check if value exists
    authenticated.value = true; // update the state to authenticated
  }

  // if token exists and url is /login redirect to homepage
  if (token.value && to?.name === 'login') {
    return navigateTo('/');
  }

  // if token doesn't exist redirect to log in
  if (!token.value && to?.name !== 'login') {
    abortNavigation();
    return navigateTo('/login');
  }
});
Enter fullscreen mode Exit fullscreen mode

预览

仓库:GitHub

文章来源:https://dev.to/rafaelmagalhaes/authentication-in-nuxt-3-375o