会话与认证
介绍
在本教程中,我们将使用 Nuxt Auth Utils 在一个全栈 Nuxt 应用中设置认证,该工具提供了便捷的客户端与服务器端会话数据管理实用工具。
该模块使用加密与封存的 cookie 来存储会话数据,因此你无需为会话数据设置数据库。
安装 nuxt-auth-utils
使用 nuxt
CLI 安装 nuxt-auth-utils
模块。
npx nuxt module add auth-utils
nuxt-auth-utils
安装为依赖并将其写入我们的 nuxt.config.ts
的 modules
部分Cookie 加密密钥
由于 nuxt-auth-utils
使用封存的 cookie 来存储会话数据,会话 cookie 会使用来自环境变量 NUXT_SESSION_PASSWORD
的密钥进行加密。
.env
中。NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
登录 API 路由
在本教程中,我们将创建一个基于静态数据实现的简单用户登录 API 路由。
让我们创建一个 /api/login
API 路由,该路由将接受包含邮箱和密码的 POST 请求体。
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') {
// 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({
statusCode: 401,
message: 'Bad credentials',
})
})
zod
依赖(npm i zod
)。登录页面
该模块暴露了一个 Vue 组合式函数,用于在应用中判断用户是否已认证:
<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>
让我们创建一个登录页面,包含一个表单将登录数据提交到我们的 /api/login
路由。
<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
路由,只有已认证用户才能访问。
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
目录下创建一个中间件。与服务器不同,客户端中间件不会自动应用到所有页面,我们需要指定要应用的页面位置。
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
将中间件应用到我们想要保护的路由。
<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 应用中成功设置了一个非常基础的用户认证与会话管理。我们还在服务器端和客户端保护了敏感路由,以确保只有已认证的用户才能访问它们。
接下来的步骤,你可以:
- 使用 20+ 个支持的 OAuth 提供商 添加认证
- 添加数据库以存储用户,参见 Nitro SQL Database 或 NuxtHub SQL Database
- 使用 密码哈希 让用户通过邮箱 & 密码注册
- 添加对 WebAuthn / Passkeys 的支持
查看开源的 atidone 仓库 以获取包含 OAuth 认证、数据库与增删改查操作的完整 Nuxt 应用示例。