会话与认证

认证是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部分

Cookie加密密钥

由于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)。
阅读更多关于nuxt-auth-utils提供的setUserSession服务端辅助函数。

登录页面

该模块暴露了一个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应用示例。