会话和认证

身份验证是 Web 应用中一个极其常见的功能需求。本示例将向您展示如何在 Nuxt 应用中实现基本的**用户注册和身份验证**。

介绍

在本示例中,我们将使用Nuxt Auth Utils来设置全栈 Nuxt 应用的身份验证,它提供了用于管理客户端和服务器端会话数据的便捷工具。

该模块使用**安全和密封的 cookie** 来存储会话数据,因此您**无需设置数据库**来存储会话数据。

安装 nuxt-auth-utils

使用 nuxt CLI 安装 nuxt-auth-utils 模块。

终端
npx nuxt module add auth-utils
此命令将把 nuxt-auth-utils 作为依赖安装,并将其推送到我们 nuxt.config.tsmodules 部分。

由于 nuxt-auth-utils 使用密封的 cookie 来存储会话数据,因此会话 cookie 是使用 NUXT_SESSION_PASSWORD 环境变量中的密钥进行加密的。

如果未设置此环境变量,在开发模式运行时,它将自动添加到您的 .env 文件中。
.env
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
在部署之前,您需要将此环境变量添加到您的生产环境中。

登录 API 路由

对于本示例,我们将创建一个简单的 API 路由,根据静态数据来登录用户。

让我们创建一个 /api/login API 路由,它将接收带有请求体中电子邮件和密码的 POST 请求。

server/api/login.post.ts
import { z } from 'zod'

const bodySchema = z.object({
  email: z.email(),
  password: z.string().min(8),
})

export default defineEventHandler(async (event) => {
  const { email, password } = await readValidatedBody(event, bodySchema.parse)

  if (email === 'admin@admin.com' && password === 'iamtheadmin') {
    // set the user session in the cookie
    // this server util is auto-imported by the auth-utils module
    await setUserSession(event, {
      user: {
        name: 'John Doe',
      },
    })
    return {}
  }
  throw createError({
    status: 401,
    message: 'Bad credentials',
  })
})
确保在您的项目中安装 zod 依赖(npm i zod)。
阅读更多关于 nuxt-auth-utils 提供的 setUserSession 服务器辅助函数。

登录页面

该模块暴露了一个 Vue setup 组合函数,用于了解用户在我们应用中是否已通过身份验证。

<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>

让我们创建一个带有表单的登录页面,用于将登录数据提交到我们的 /api/login 路由。

app/pages/login.vue
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
  email: '',
  password: '',
})
async function login () {
  try {
    await $fetch('/api/login', {
      method: 'POST',
      body: credentials,
    })

    // Refresh the session on client-side and redirect to the home page
    await refreshSession()
    await navigateTo('/')
  } catch {
    alert('Bad credentials')
  }
}
</script>

<template>
  <form @submit.prevent="login">
    <input
      v-model="credentials.email"
      type="email"
      placeholder="Email"
    >
    <input
      v-model="credentials.password"
      type="password"
      placeholder="Password"
    >
    <button type="submit">
      Login
    </button>
  </form>
</template>

保护 API 路由

保护服务器路由是确保数据安全的关键。客户端中间件对用户有帮助,但如果没有服务器端保护,您的数据仍然可能被访问。保护任何包含敏感数据的路由至关重要,如果用户未登录,我们应该返回 401 错误。

auth-utils 模块提供了 requireUserSession 实用函数,以帮助确保用户已登录并拥有有效的会话。

让我们创建一个示例 /api/user/stats 路由,只有经过身份验证的用户才能访问。

server/api/user/stats.get.ts
export default defineEventHandler(async (event) => {
  // make sure the user is logged in
  // This will throw a 401 error if the request doesn't come from a valid user session
  const { user } = await requireUserSession(event)

  // TODO: Fetch some stats based on the user

  return {}
})

保护应用路由

有了服务器端路由,我们的数据是安全的,但如果不做任何其他操作,未经验证的用户在尝试访问 /users 页面时可能会看到一些奇怪的数据。我们应该创建一个客户端中间件来在客户端保护该路由并重定向用户到登录页面。

nuxt-auth-utils 提供了一个便捷的 useUserSession 组合函数,我们将用它来检查用户是否已登录,如果未登录,则重定向他们。

我们将在 /middleware 目录下创建一个中间件。与服务器端不同,客户端中间件不会自动应用于所有端点,我们需要指定要在哪里应用它。

app/middleware/authenticated.ts
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useUserSession()

  // redirect the user to the login screen if they're not authenticated
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

首页

既然我们有了用于保护路由的应用中间件,我们就可以在显示已验证用户信息的主页上使用它。如果用户未通过身份验证,他们将被重定向到登录页面。

我们将使用 definePageMeta 来将中间件应用于我们想要保护的路由。

app/pages/index.vue
<script setup lang="ts">
definePageMeta({
  middleware: ['authenticated'],
})

const { user, clear: clearSession } = useUserSession()

async function logout () {
  await clearSession()
  await navigateTo('/login')
}
</script>

<template>
  <div>
    <h1>Welcome {{ user.name }}</h1>
    <button @click="logout">
      Logout
    </button>
  </div>
</template>

我们还添加了一个**注销**按钮来清除会话并将用户重定向到登录页面。

结论

我们已成功在 Nuxt 应用中设置了非常基础的用户身份验证和会话管理。我们还保护了服务器端和客户端的敏感路由,以确保只有通过身份验证的用户才能访问它们。

接下来的步骤,您可以

的**支持**查看开源的atidone 仓库