---
title: "会话与认证"
description: "认证是 Web 应用中一个非常常见的需求。本教程将向你展示如何在 Nuxt 应用中实现基本的用户注册与认证。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.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 [终端]
npx nuxt module add auth-utils
```

<callout>

此命令会将 `nuxt-auth-utils` 安装为依赖并将其写入我们的 `nuxt.config.ts` 的 `modules` 部分

</callout>

## Cookie 加密密钥

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

<note>

如果未设置，该环境变量将在开发模式下自动添加到你的 `.env` 中。

</note>

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

<important>

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

</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`）。

</callout>

<tip to="https://github.com/atinux/nuxt-auth-utils#server-utils">

阅读更多关于 `nuxt-auth-utils` 暴露的 `setUserSession` 服务器辅助函数的信息。

</tip>

## 登录页面

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

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

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

```vue [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,
    })

    // 刷新客户端的会话，并重定向到首页
    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/4.x/directory-structure/app/middleware)，在客户端保护该路由并将用户重定向到登录页面。

`nuxt-auth-utils` 提供了一个方便的 `useUserSession` 组合式函数，我们将用它来检查用户是否已登录，并在未登录时重定向他们。

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

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

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

## 主页

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

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

```vue [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>欢迎 {{ 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.zhcndoc.com/guide/database) 或 [NuxtHub SQL Database](https://hub.nuxt.com/docs/features/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 认证、数据库与增删改查操作的完整 Nuxt 应用示例。


## Sitemap

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