会话和认证

认证在 web 应用中是一个非常常见的需求。本食谱将向您展示如何在您的 Nuxt 应用中实现基本的用户注册和认证。

简介

在本食谱中,我们将使用 Nuxt Auth Utils 设置全栈 Nuxt 应用中的认证,该工具提供了管理客户端和服务端会话数据的便捷工具。

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

安装 nuxt-auth-utils

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

Terminal
npx nuxi@latest module add auth-utils
此命令将安装 nuxt-auth-utils 作为依赖项,并将其推送到我们的 nuxt.config.tsmodules 部分

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

如果未设置,此环境变量将在开发模式下运行时自动添加到您的 .env 中。
.env
NUXT_SESSION_PASSWORD=一个随机密码,至少包含32个字符
您需要在部署之前将此环境变量添加到生产环境中。

登录 API 路由

对于本食谱,我们将创建一个简单的 API 路由,以基于静态数据进行用户登录。

让我们创建一个 /api/login API 路由,它将接受一个包含电子邮件和密码的 POST 请求。

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

const bodySchema = z.object({
  email: z.string().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') {
    // 在 Cookie 中设置用户会话
    // 这个服务器工具是由 auth-utils 模块自动导入的
    await setUserSession(event, {
      user: {
        name: 'John Doe'
      }
    })
    return {}
  }
  throw createError({
    statusCode: 401,
    message: '凭据错误'
  })
})
确保在您的项目中安装 zod 依赖项(npm i zod)。
了解有关 setUserSession 服务器助手的更多信息,该助手由 nuxt-auth-utils 提供。

登录页面

该模块提供了一个 Vue 可组合函数,可以知道用户是否在我们的应用中已认证:

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

我们来创建一个登录页面,包含一个表单以将登录数据提交到我们的 /api/login 路由。

pages/login.vue
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
  email: '',
  password: '',
})
async function login() {
  $fetch('/api/login', {
    method: 'POST',
    body: credentials
  })
  .then(async () => {
    // 在客户端刷新会话并重定向到主页
    await refreshSession()
    await navigateTo('/')
  })
  .catch(() => alert('凭据错误'))
}
</script>

<template>
  <form @submit.prevent="login">
    <input v-model="credentials.email" type="email" placeholder="电子邮件" />
    <input v-model="credentials.password" type="password" placeholder="密码" />
    <button type="submit">登录</button>
  </form>
</template>

保护 API 路由

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

auth-utils 模块提供了 requireUserSession 工具函数来帮助确保用户已登录并且具有活动会话。

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

server/api/user/stats.get.ts
export default defineEventHandler(async (event) => {
  // 确保用户已登录
  // 如果请求不是来自有效用户会话,将抛出 401 错误
  const { user } = await requireUserSession(event)

  // TODO: 根据用户获取一些统计数据

  return {}
});

保护应用路由

确保我们的数据通过服务器端路由保护,但如果不采取任何其他措施,未认证的用户在尝试访问 /users 页面时可能会获取到一些奇怪的数据。我们应该创建一个 客户端中间件 来在客户端保护该路由,并将用户重定向到登录页面。

nuxt-auth-utils 提供了一个方便的 useUserSession 可组合函数,我们将使用它来检查用户是否已登录,并在他们未登录的情况下将其重定向。

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

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

  // 如果用户未认证,重定向到登录页面
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

主页

现在我们有了应用中间件来保护我们的路由,我们可以在主页上使用它,显示我们的认证用户信息。如果用户未认证,他们将被重定向到登录页面。

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

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>欢迎 {{ user.name }}</h1>
    <button @click="logout">注销</button>
  </div>
</template>

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

结论

我们成功地在我们的 Nuxt 应用中设置了一个非常基本的用户认证和会话管理。我们还保护了服务器和客户端的敏感路由,以确保只有经过认证的用户才能访问它们。

作为后续步骤,您可以:

查看开源的 atidone 仓库,了解带有 OAuth 认证、数据库和 CRUD 操作的完整 Nuxt 应用示例。