---
title: "会话与认证"
description: "认证是 Web 应用中极为常见的需求。本教程将展示如何在 Nuxt 应用中实现基本的用户注册和认证。"
canonical_url: "https://nuxt.zhcndoc.com/docs/3.x/guide/recipes/sessions-and-authentication"
---
# 会话与认证

> 认证是 Web 应用中极为常见的需求。本教程将展示如何在 Nuxt 应用中实现基本的用户注册和认证。

## 介绍

在本教程中，我们将使用 [Nuxt Auth Utils](https://github.com/Atinux/nuxt-auth-utils) 在一个全栈 Nuxt 应用中设置认证。该工具库为管理客户端和服务器端的会话数据提供了便捷的工具。

该模块使用安全且密封的 Cookie 来存储会话数据，因此你无需设置数据库来存储会话信息。

## 安装 nuxt-auth-utils

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

```bash [Terminal]
npx nuxt module add auth-utils
```

::callout
此命令会安装 `nuxt-auth-utils` 作为依赖，并将其添加到 `nuxt.config.ts` 的 `modules` 部分。
::

## Cookie 加密密钥

由于 `nuxt-auth-utils` 使用密封的 Cookie 存储会话数据，会话 Cookie 会通过环境变量 `NUXT_SESSION_PASSWORD` 中的密钥进行加密。

::note
如果未设置该环境变量，开发模式下运行时会自动将其添加到你的 `.env` 文件中。
::

```ini [.env]
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
```

::important
你需要在部署之前将此环境变量添加到生产环境中。
::

## 登录 API 路由

本教程中，我们将创建一个简单的 API 路由，根据静态数据实现用户登录功能。

创建一个 `/api/login` 的 API 路由，接受包含邮箱和密码的 POST 请求体。

```ts [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') {
    // 设置用户会话到 Cookie
    // 该服务器工具由 auth-utils 模块自动导入
    await setUserSession(event, {
      user: {
        name: 'John Doe',
      },
    })
    return {}
  }
  throw createError({
    status: 401,
    message: '认证失败',
  })
})
```

::callout
请确保在项目中安装了 `zod` 依赖（`npm i zod`）。
::

::tip{to="https://github.com/atinux/nuxt-auth-utils#server-utils"}
深入了解由 `nuxt-auth-utils` 提供的 `setUserSession` 服务器辅助函数。
::

## 登录页面

该模块暴露了一个 Vue 组合函数，用于判断用户是否已认证：

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

我们来创建一个登录页面，包含一个表单用于提交登录数据到 `/api/login` 路由。

```vue [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` 路由示例，仅允许认证用户访问：

```ts [server/api/user/stats.get.ts]
export default defineEventHandler(async (event) => {
  // 确认用户已登录
  // 若请求未来自有效用户会话，将抛出 401 错误
  const { user } = await requireUserSession(event)

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

  return {}
})
```

## 保护应用路由

服务器端路由已就位，我们的数据便得到了保护；但如果不做其他处理，未认证用户在尝试访问 `/users` 页面时可能会看到一些奇怪的数据。我们应创建一个[客户端中间件](https://nuxt.zhcndoc.com/docs/3.x/directory-structure/middleware)，在客户端保护该路由，并将用户重定向到登录页面。

`nuxt-auth-utils` 提供了便捷的 `useUserSession` 组合函数，我们会用它检测用户登录状态，并在未登录时重定向。

在 `/middleware` 目录下创建中间件。与服务器不同，客户端中间件不会自动应用到所有路由，需手动指定应用范围。

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

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

## 首页

现在我们有了客户端中间件保护路由，可以在首页显示认证用户信息。若用户未认证，将被重定向到登录页。

我们将使用 [`definePageMeta`](https://nuxt.zhcndoc.com/docs/3.x/api/utils/define-page-meta) 将中间件应用到我们想要保护的路由。

```vue [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 应用中设置了基础的用户认证和会话管理。同时保护了服务器端和客户端的敏感路由，确保只有认证用户可以访问。

接下来，你可以：

- 使用[20 多个受支持的 OAuth 提供商](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#supported-oauth-providers)添加认证
- 添加数据库来存储用户，参见 [Nitro SQL Database](https://nitro.build/guide/database) 或 [NuxtHub SQL Database](https://hub.nuxt.com/docs/database)
- 使用[密码哈希](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#password-hashing)支持用户通过邮箱和密码注册
- 添加对 [WebAuthn / Passkeys](https://github.com/atinux/nuxt-auth-utils?tab=readme-ov-file#webauthn-passkey) 的支持

请查看开源的 [atidone 仓库](https://github.com/atinux/atidone)，这里有一个包含 OAuth 认证、数据库和 CRUD 操作的 Nuxt 应用完整示例。


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
