会话和身份验证
简介
¥Introduction
在本教程中,我们将使用 Nuxt 身份验证实用程序 在全栈 Nuxt 应用中设置身份验证,Nuxt 身份验证实用程序 提供了便捷的实用程序来管理客户端和服务器端会话数据。
¥In this recipe we'll be setting up authentication in a full-stack Nuxt app using Nuxt Auth Utils which provides convenient utilities for managing client-side and server-side session data.
该模块使用安全密封的 Cookie 来存储会话数据,因此你无需设置数据库来存储会话数据。
¥The module uses secured & sealed cookies to store session data, so you don't need to setup a database to store session data.
安装 nuxt-auth-utils
¥Install nuxt-auth-utils
使用 nuxi
CLI 安装 nuxt-auth-utils
模块。
¥Install the nuxt-auth-utils
module using the nuxi
CLI.
npx nuxi@latest module add auth-utils
nuxt-auth-utils
作为依赖,并将其推送到 nuxt.config.ts
的 modules
部分。¥This command will install nuxt-auth-utils
as dependency and push it in the modules
section of our nuxt.config.ts
Cookie 加密密钥
¥Cookie Encryption Key
由于 nuxt-auth-utils
使用密封的 Cookie 来存储会话数据,因此会话 Cookie 使用来自 NUXT_SESSION_PASSWORD
环境变量的密钥进行加密。
¥As nuxt-auth-utils
uses sealed cookies to store session data, session cookies are encrypted using a secret key from the NUXT_SESSION_PASSWORD
environment variable.
.env
中。NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
登录 API 路由
¥Login API Route
为了使其按预期工作,建议不要将 作为页面组件的根元素。
¥For this recipe, we'll create a simple API route to sign-in a user based on static data.
让我们创建一个 /api/login
API 路由,它将接受请求正文中包含邮箱和密码的 POST 请求。
¥Let's create a /api/login
API route that will accept a POST request with the email and password in the request body.
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'
})
})
npm i zod
)中安装 zod
依赖。¥Make sure to install the zod
dependency in your project (npm i zod
).nuxt-auth-utils
公开的 setUserSession
服务器助手的信息。¥Read more about the setUserSession
server helper exposed by nuxt-auth-utils
.登录页面
¥Login Page
模块公开一个 Vue 可组合函数,用于识别用户是否在我们的应用中进行了身份验证:
¥The module exposes a Vue composable to know if a user is authenticated in our application:
<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>
让我们创建一个带有表单的登录页面,以便将登录数据提交到我们的 /api/login
路由。
¥Let's create a login page with a form to submit the login data to our /api/login
route.
<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 () => {
// 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 路由
¥Protect API Routes
保护服务器路由是确保数据安全的关键。客户端中间件对用户很有帮助,但如果没有服务器端保护,你的数据仍然可以被访问。保护任何包含敏感数据的路由至关重要,如果用户未登录这些路由,我们应该返回 401 错误。
¥Protecting server routes is key to making sure your data is safe. Client-side middleware is helpful for the user, but without server-side protection your data can still be accessed. It is critical to protect any routes with sensitive data, we should return a 401 error if the user is not logged in on those.
auth-utils
模块提供 requireUserSession
实用函数,帮助确保用户已登录并拥有活动会话。
¥The auth-utils
module provides the requireUserSession
utility function to help make sure that users are logged in and have an active session.
让我们创建一个只有经过身份验证的用户才能访问的 /api/user/stats
路由示例。
¥Let's create an example of a /api/user/stats
route that only authenticated users can access.
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 {}
});
保护应用路由
¥Protect App Routes
通过服务器端路由,我们的数据是安全的,但如果不采取任何其他措施,未经身份验证的用户在尝试访问 /users
页面时可能会收到一些奇怪的数据。我们应该创建一个 客户端中间件 来保护客户端的路由,并将用户重定向到登录页面。
¥Our data is safe with the server-side route in place, but without doing anything else, unauthenticated users would probably get some odd data when trying to access the /users
page. We should create a client-side middleware to protect the route on the client side and redirect users to the login page.
nuxt-auth-utils
提供了一个便捷的 useUserSession
可组合函数,我们将使用它来检查用户是否已登录,并在未登录时重定向用户。
¥nuxt-auth-utils
provides a convenient useUserSession
composable which we'll use to check if the user is logged in, and redirect them if they are not.
我们将在 /middleware
目录中创建一个中间件。与服务器不同,客户端中间件不会自动应用于所有端点,我们需要指定其应用位置。
¥We'll create a middleware in the /middleware
directory. Unlike on the server, client-side middleware is not automatically applied to all endpoints, and we'll need to specify where we want it applied.
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()
// redirect the user to the login screen if they're not authenticated
if (!loggedIn.value) {
return navigateTo('/login')
}
})
主页
¥Home Page
现在我们有了应用中间件来保护路由,我们可以在主页上使用它来显示经过身份验证的用户信息。如果用户未通过身份验证,他们将被重定向到登录页面。
¥Now that we have our app middleware to protect our routes, we can use it on our home page that display our authenticated user information. If the user is not authenticated, they will be redirected to the login page.
我们将使用 definePageMeta
将中间件应用于我们想要保护的路由。
¥We'll use definePageMeta
to apply the middleware to the route that we want to protect.
<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>
我们还添加了一个注销按钮,用于清除会话并将用户重定向到登录页面。
¥We also added a logout button to clear the session and redirect the user to the login page.
结论
¥Conclusion
我们已经在 Nuxt 应用中成功设置了非常基本的用户身份验证和会话管理。我们还在服务器端和客户端保护了敏感路由,以确保只有经过身份验证的用户才能访问它们。
¥We've successfully set up a very basic user authentication and session management in our Nuxt app. We've also protected sensitive routes on the server and client side to ensure that only authenticated users can access them.
接下来,你可以:
¥As next steps, you can:
- 使用 20 多个受支持的 OAuth 提供商 添加身份验证
- 添加数据库来存储用户,请参阅 Nitro SQL 数据库 或 NuxtHub SQL 数据库
- 允许用户使用 密码哈希 使用邮箱和密码注册
- 添加对 WebAuthn / 密钥 的支持
查看开源 atidone 代码库,获取包含 OAuth 身份验证、数据库和 CRUD 操作的 Nuxt 应用的完整示例。
¥Checkout the open source atidone repository for a full example of a Nuxt app with OAuth authentication, database and CRUD operations.