type
Post
status
Published
date
Feb 8, 2026
slug
nextjs-shopping-platform-004
summary
tags
Next.js
category
编程学习
icon
password
😀

1. Section Intro

Now we're going to add authentication to our application. We will use NextAuth.js to handle the authentication. NextAuth.js is a complete open-source authentication solution for Next.js applications. It is designed to work with any authentication provider and supports a wide range of authentication methods. 现在我们将为应用程序添加身份验证功能。我们将使用 NextAuth.js 来处理身份验证。NextAuth.js 是一个完整的开源身份验证解决方案,专为 Next.js 应用程序设计。它可以与任何身份验证提供商配合使用,并支持多种身份验证方法。
We are going to use a local email/password provider. We will use the jwt strategy, which uses JSON Web Tokens and is stateless and scalable. A JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. JWTs can be signed using a secret, which will be used to verify the token's authenticity. We will use the jwt strategy to generate a JWT token and store it in the session cookie. 我们将使用本地邮箱/密码提供商。我们将使用 jwt 策略,该策略使用 JSON Web Token,具有无状态和可扩展的特点。JSON Web Token(JWT)是一种紧凑的、URL 安全的方式,用于表示要在两方之间传输的声明。JWT 可以使用密钥进行签名,该密钥将用于验证令牌的真实性。我们将使用 jwt 策略生成 JWT 令牌并将其存储在 session Cookie 中。
We're going to create a few more Prisma models that relate to users and authentication including the User model, the Session model and the VerificationToken model. 我们将创建更多与用户和身份验证相关的 Prisma 模型,包括 User 模型、Session 模型和 VerificationToken 模型。
Once that's setup we'll create the server actions to sign in and out and then create the form and the signout button. 设置完成后,我们将创建用于登录和退出的服务器操作,然后创建表单和退出按钮。
We're using Zod for validation and hooking that up is easy. We don't have to do any manual validation as we just link the form to the Zod schema. 我们使用 Zod 进行验证,连接它非常简单。我们不需要进行任何手动验证,只需将表单链接到 Zod 模式即可。
Then we'll setup user registration so that users can create and account. 然后我们将设置用户注册功能,以便用户可以创建账户。
I want to go over the flow of our authentication after we look at the individual tasks here. 在了解了这里的各个任务之后,我想介绍一下我们身份验证的流程。
So first we are going to create some Prisma models that have to do with users and authentication including the User, Account, Session and VerificationToken models. We're then going to seed some user data. We've already seeded product data now I want to add users. 因此,首先我们将创建一些与用户和身份验证相关的 Prisma 模型,包括 User、Account、Session 和 VerificationToken 模型。然后我们将填充一些用户数据。我们已经填充了产品数据,现在我想添加用户。
Then we'll install and setup Next Auth. We'll create the sign in and sign out actions on the server. Then create the sign in page and form. We'll add the sign out button. We're going to setup a Zod schema for validation. Create our sign up page and form. Handle errors and then add a JWT callback to the Next Auth so that we can customize the token that is sent to the client. 然后我们将安装并设置 Next Auth。我们将在服务器上创建登录和退出操作。然后创建登录页面和表单。我们将添加退出按钮。我们将设置一个 Zod 模式用于验证。创建我们的注册页面和表单。处理错误,然后向 Next Auth 添加 JWT 回调,以便我们可以自定义发送给客户端的令牌。

Authentication Flow Explained 身份验证流程说明

Here is a straightforward explanation of how authentication works in our application: 以下是我们应用程序中身份验证工作方式的简单说明:
  • Users log in with email and password.
  • 用户使用邮箱和密码登录。
  • The credentials are checked against the database using Prisma.
  • 使用 Prisma 在数据库中验证凭据。
  • If the credentials are correct, a JWT (JSON Web Token) is created for the user.
  • 如果凭据正确,将为用户创建一个 JWT(JSON Web Token)。
  • The JWT contains the user's info (like ID and role) and is encrypted and stored in the session cookie.
  • JWT 包含用户信息(如 ID 和角色),并被加密存储在会话 Cookie 中。
  • The JWT is sent with every request to authenticate the user.
  • JWT 随每个请求一起发送以验证用户身份。
  • The JWT is used to create a session that holds the user's details (ID, name, email).
  • JWT 用于创建一个会话,保存用户的详细信息(ID、姓名、邮箱)。
  • The session allows the application to identify the logged-in user without needing to query the database again.
  • 会话允许应用程序识别已登录用户,而无需再次查询数据库。
  • The JWT and session data expire after a set time (e.g., 30 days).
  • JWT 和会话数据在设定时间后过期(例如 30 天)。
That's the gist of how this will all work. I will explain more about the process as we go. 这就是整个工作流程的要点。随着我们的进行,我将解释更多关于这个过程的内容。

2. Prisma User-Related Models Prisma 用户相关模型

We need to create 4 new models that have to do with users and authentication. There is a documentation page with the suggested models to use here: https://authjs.dev/getting-started/adapters/prisma. Click on the "Prisma" tab. 我们需要创建 4 个与用户和身份验证相关的新模型。这里有一个文档页面,提供了建议使用的模型:https://authjs.dev/getting-started/adapters/prisma。点击 "Prisma" 标签页。
This shows some of the basic fields, but we will customize it a bit. 这显示了一些基本字段,但我们会进行一些自定义。
Open the schema.prisma file and add the User model: 打开 schema.prisma 文件并添加 User 模型:

Field Descriptions 字段说明

  • id: This is the primary key for the user. It is a UUID. It will be generated automatically by Prisma.
  • id:这是用户的主键。它是一个 UUID。它将由 Prisma 自动生成。
  • name: This is the name of the user. It is a string and has a default value of "NO_NAME".
  • name:这是用户的姓名。它是一个字符串,默认值为 "NO_NAME"。
  • email: This is the email of the user. It is a string and is unique. It is also indexed.
  • email:这是用户的邮箱。它是一个字符串,并且是唯一的。它也被索引。
  • password: This is the password of the user. It is a string and is optional. The reason it is optional is because there may be a login with Oauth and something like Google or Github and there will be no password.
  • password:这是用户的密码。它是一个字符串,并且是可选的。它是可选的原因是,可能会有使用 OAuth 和 Google 或 Github 等方式的登录,这种情况下不会有密码。
  • role: This is the role of the user. It is a string and has a default value of "user".
  • role:这是用户的角色。它是一个字符串,默认值为 "user"。
  • emailVerified: This is the date and time that the user's email was verified. It is a DateTime and is optional.
  • emailVerified:这是用户邮箱验证的日期和时间。它是一个 DateTime,并且是可选的。
  • image: This is the image of the user. It is a string and is optional.
  • image:这是用户的头像。它是一个字符串,并且是可选的。
  • address: This is the address of the user. It is a JSON object and is optional.
  • address:这是用户的地址。它是一个 JSON 对象,并且是可选的。
  • paymentMethod: This is the payment method of the user. It is a string and is optional.
  • paymentMethod:这是用户的支付方式。它是一个字符串,并且是可选的。
  • createdAt: This is the date and time that the user was created. It is a DateTime and has a default value of the current date and time.
  • createdAt:这是用户创建的日期和时间。它是一个 DateTime,默认值为当前日期和时间。
  • updatedAt: This is the date and time that the user was last updated. It is a DateTime and is updated automatically by Prisma.
  • updatedAt:这是用户最后更新的日期和时间。它是一个 DateTime,由 Prisma 自动更新。
  • account: This is the account of the user. It is an array of Account objects.
  • account:这是用户的账户。它是一个 Account 对象数组。
  • session: This is the session of the user. It is an array of Session objects.
  • session:这是用户的会话。它是一个 Session 对象数组。

Prisma Account Model Prisma 账户模型

Here is the model for the Account: 以下是 Account 的模型:

Field Descriptions 字段说明

  • userId: This is the user id of the user. It is a string and is a foreign key to the User model.
  • userId:这是用户的用户 ID。它是一个字符串,是 User 模型的外键。
  • type: This is the type of the account. It is a string.
  • type:这是账户的类型。它是一个字符串。
  • provider: This is the provider of the account. It is a string.
  • provider:这是账户的提供商。它是一个字符串。
  • providerAccountId: This is the provider account id of the account. It is a string.
  • providerAccountId:这是账户的提供商账户 ID。它是一个字符串。
  • refresh_token: This is the refresh token of the account. It is a string and is optional. This is used to get a new access token when the current one expires.
  • refresh_token:这是账户的刷新令牌。它是一个字符串,并且是可选的。用于在当前令牌过期时获取新的访问令牌。
  • access_token: This is the access token of the account. It is a string and is optional. This is used to make requests to the provider's API.
  • access_token:这是账户的访问令牌。它是一个字符串,并且是可选的。用于向提供商的 API 发出请求。
  • expires_at: This is the expiration time of the access token. It is an integer and is optional. This is used to determine when the access token expires.
  • expires_at:这是访问令牌的过期时间。它是一个整数,并且是可选的。用于确定访问令牌何时过期。
  • token_type: This is the type of the token. It is a string and is optional. This is used to determine the type of the token.
  • token_type:这是令牌的类型。它是一个字符串,并且是可选的。用于确定令牌的类型。
  • scope: This is the scope of the token. It is a string and is optional. This is used to determine the scope of the token.
  • scope:这是令牌的作用域。它是一个字符串,并且是可选的。用于确定令牌的作用域。
  • id_token: This is the id token of the account. It is a string and is optional. This is used to verify the identity of the user.
  • id_token:这是账户的 ID 令牌。它是一个字符串,并且是可选的。用于验证用户的身份。
  • session_state: This is the session state of the account. It is a string and is optional. This is used to determine the state of the session.
  • session_state:这是账户的会话状态。它是一个字符串,并且是可选的。用于确定会话的状态。
  • createdAt: This is the date and time that the account was created. It is a DateTime and has a default value of the current date and time.
  • createdAt:这是账户创建的日期和时间。它是一个 DateTime,默认值为当前日期和时间。
  • updatedAt: This is the date and time that the account was last updated. It is a DateTime and is updated automatically by Prisma.
  • updatedAt:这是账户最后更新的日期和时间。它是一个 DateTime,由 Prisma 自动更新。
  • user: This is the user of the account. It is a User object. It is a foreign key to the User model. The map attribute is used to specify the name of the foreign key constraint.
  • user:这是账户的用户。它是一个 User 对象。它是 User 模型的外键。map 属性用于指定外键约束的名称。
  • @@id: This is the primary key for the account. It is a composite primary key. It is made up of the provider and providerAccountId fields. In many OAuth systems, the combination of the provider (e.g., Google, GitHub) and the providerAccountId (the user's ID from the provider) is guaranteed to be unique, so it makes sense to use this as a primary key. Having a separate id field would be redundant.
  • @@id:这是账户的主键。它是一个复合主键。它由 providerproviderAccountId 字段组成。在许多 OAuth 系统中,提供商(例如 Google、GitHub)和 providerAccountId(来自提供商的用户 ID)的组合是保证唯一的,因此将其用作主键是有意义的。拥有一个单独的 id 字段将是多余的。

Prisma Session Model Prisma 会话模型

Here is the model for the Session: 以下是 Session 的模型:

Field Descriptions 字段说明

  • sessionToken: This is the session token of the session. It is a string and is the primary key.
  • sessionToken:这是会话的会话令牌。它是一个字符串,是主键。
  • userId: This is the user id of the user. It is a string and is a foreign key to the User model.
  • userId:这是用户的用户 ID。它是一个字符串,是 User 模型的外键。
  • expires: This is the expiration time of the session. It is a DateTime and is a timestamp with 6 decimal places. This is used to determine when the session expires.
  • expires:这是会话的过期时间。它是一个 DateTime,是带有 6 位小数的时间戳。用于确定会话何时过期。
  • user: This is the user of the session. It is a User object. It is a foreign key to the User model. The map attribute is used to specify the name of the foreign key constraint.
  • user:这是会话的用户。它是一个 User 对象。它是 User 模型的外键。map 属性用于指定外键约束的名称。

Prisma Verification Token Model Prisma 验证令牌模型

Here is the model for the Verification Token: 以下是 Verification Token 的模型:

Field Descriptions 字段说明

  • identifier: This is the identifier of the verification token. It is a string and is part of the primary key.
  • identifier:这是验证令牌的标识符。它是一个字符串,是主键的一部分。
  • token: This is the token of the verification token. It is a string and is part of the primary key.
  • token:这是验证令牌的令牌。它是一个字符串,是主键的一部分。
  • expires: This is the expiration time of the verification token. It is a DateTime and is used to determine when the verification token expires.
  • expires:这是验证令牌的过期时间。它是一个 DateTime,用于确定验证令牌何时过期。
  • @@id: This is the primary key for the verification token. It is a composite primary key. It is made up of the identifier and token fields.
  • @@id:这是验证令牌的主键。它是一个复合主键。它由 identifiertoken 字段组成。

Generate the Prisma Client 生成 Prisma 客户端

Since we made these changes, we need to regenerate the Prisma Client. To do this, stop the Next.js server and run the following command: 由于我们进行了这些更改,我们需要重新生成 Prisma 客户端。为此,停止 Next.js 服务器并运行以下命令:
This will generate the Prisma Client with the new models. If you do not stop the server, you will probably get an error. 这将生成带有新模型的 Prisma 客户端。如果你不停止服务器,可能会收到错误。

Prisma Migration Prisma 迁移

We also need to create a migration for the new models. To do this, run the following command: 我们还需要为新模型创建迁移。为此,运行以下命令:
This will create a new migration with the name add_user_based_tables. You can change the name if you want. 这将创建一个名为 add_user_based_tables 的新迁移。如果你愿意,可以更改名称。
Your database should now be in sync with the new models. 你的数据库现在应该与新模型同步。
You can check Prisma Studio to see the new tables and fields. You will need to restart it if you have it running. You can do this by running the following command: 你可以检查 Prisma Studio 以查看新的表和字段。如果你正在运行它,需要重新启动。你可以通过运行以下命令来执行此操作:
Then visitt http://localhost:5555 in your browser. 然后在浏览器中访问 http://localhost:5555
 

3. Seed User Data 填充用户数据

Let's add a couple sample users to the database. 让我们向数据库中添加几个示例用户。

Install bcrypt-ts-edge 安装 bcrypt-ts-edge

If you look at the sample data, the passwords are being hashed. We are going to use the bcrypt-ts-edge package. It is a TypeScript-native implementation of bcrypt, designed for edge environments such as Cloudflare Workers, Vercel Edge Functions, or any serverless platforms that don't support native Node.js APIs. You could just as well install and use the bcryptjs package, but we're going to use this for easy deployment later. 如果你查看示例数据,密码正在被哈希处理。我们将使用 bcrypt-ts-edge 包。它是 bcrypt 的 TypeScript 原生实现,专为边缘环境设计,如 Cloudflare Workers、Vercel Edge Functions 或任何不支持原生 Node.js API 的无服务器平台。你也可以安装并使用 bcryptjs 包,但我们使用这个是为了以后更容易部署。
Run the following command to install the package: 运行以下命令安装该包:
Open the db/sample-data.ts file and add the following code: 打开 db/sample-data.ts 文件并添加以下代码:

Update the Seeder 更新填充脚本

Open the db/seed.ts file and update it with the following code: 打开 db/seed.ts 文件并用以下代码更新它:
Now, in addition to the products, we are also seeding the users. 现在,除了产品之外,我们还在填充用户数据。
Open Prisma Studio and look at the User table. You should see the two users we just seeded. 打开 Prisma Studio 并查看 User 表。你应该能看到我们刚刚填充的两个用户。

4. Next Auth Setup Next Auth 设置

We will be using Next Auth for authentication in our application. Next Auth is a complete open-source authentication solution for Next.js applications. It is pretty easy to set up and provides a lot of features out of the box. You can use email/password, but you can also use a huge amount of providers such as Google, Facebook, Twitter, GitHub, etc. 我们将在应用程序中使用 Next Auth 进行身份验证。Next Auth 是一个完整的开源身份验证解决方案,专为 Next.js 应用程序设计。它非常容易设置,并提供许多开箱即用的功能。你可以使用邮箱/密码,也可以使用大量提供商,如 Google、Facebook、Twitter、GitHub 等。
Our app will use sessions and JWT tokens to authenticate users. We will be using the JWT strategy for our authentication. This means that when a user logs in, they will get a JWT token that will be stored in a cookie. This token will be used to authenticate the user on the server side. The token will be signed and encrypted with a secret key. 我们的应用程序将使用会话和 JWT 令牌来验证用户身份。我们将使用 JWT 策略进行身份验证。这意味着当用户登录时,他们将获得一个 JWT 令牌,该令牌将存储在 Cookie 中。此令牌将用于在服务器端验证用户身份。该令牌将使用密钥进行签名和加密。
We will also be setting up the Prisma adapter to use with Next Auth. The Prisma adapter will allow us to use Prisma to store the user information in our database. 我们还将设置 Prisma 适配器以与 Next Auth 一起使用。Prisma 适配器将允许我们使用 Prisma 将用户信息存储在数据库中。

Next Auth Installation Next Auth 安装

I want to use version 5, which is the latest version at the time of writing this. In order to prevent some warnings that we may get with Next 15, I am going to run the following command: 我想使用版本 5,这是撰写本文时的最新版本。为了防止 Next 15 可能出现的一些警告,我将运行以下命令:
You may be fine just running npm install next-auth depending on when you are watching this. At this moment in time (October 2024) I get a warning about asynchronous headers. This update fixes it. Also, there were some issues with the react paypal package, so I had to add the --legacy-peer-deps flag. If you want to just try it out, you can just run npm install next-auth and if you run into any issues like I just explained, you can just run the command above. 根据你观看的时间,直接运行 npm install next-auth 可能也可以。目前(2024年10月)我收到关于异步 headers 的警告。此更新修复了它。此外,react paypal 包存在一些问题,所以我不得不添加 --legacy-peer-deps 标志。如果你想尝试一下,可以直接运行 npm install next-auth,如果遇到我刚才解释的任何问题,可以运行上面的命令。
I will change this documentation when the latest version of Next Auth is released. 当 Next Auth 的最新版本发布时,我将更新此文档。

Prisma Adapter Prisma 适配器

There is an adapter that we can use to smoothly integrate Next Auth with Prisma. 有一个适配器可以用来将 Next Auth 与 Prisma 平滑集成。
Open a terminal and run the following command: 打开终端并运行以下命令:

Generate a Secret Key 生成密钥

Next Auth requires a secret key to be set. This key is used to sign and optionally encrypt the JWT session token. Next Auth 需要设置一个密钥。此密钥用于签名和可选地加密 JWT 会话令牌。
You can generate a secret key by running the following command: 你可以通过运行以下命令生成密钥:
Once you get the secret key, create a .env file in the root of the project and add the key. It will look something like this: 获得密钥后,在项目的根目录中创建一个 .env 文件并添加密钥。它看起来像这样:
You also want to add the following environment variables to the .env file: 你还需要将以下环境变量添加到 .env 文件中:
More on this here 更多内容请参见这里

Create Our Auth File 创建认证文件

We need to create our main authentication file. This is where we will setup our providers, callbacks, and other authentication related things. This is the meat and potatos of the authentication functionality. The documentation for this can be found here. 我们需要创建我们的主认证文件。这是我们将设置提供商、回调和其他与认证相关的东西的地方。这是认证功能的核心。相关文档可以在这里找到。
Create a file in the root called auth.ts and add the following imports: 在根目录中创建一个名为 auth.ts 的文件并添加以下导入:
  • compareSync is a Bcrypt function that we will use to compare the password from the database with the password from the form.
  • compareSync 是一个 Bcrypt 函数,我们将使用它来比较数据库中的密码和表单中的密码。
  • NextAuthConfig is the type for the Next Auth configuration.
  • NextAuthConfig 是 Next Auth 配置的类型。
  • NextAuth is the Next Auth function that we will use to setup our authentication.
  • NextAuth 是 Next Auth 函数,我们将使用它来设置我们的认证。
  • CredentialsProvider is the provider that we will use to authenticate users. This just uses a username and password. There are many other providers that you can use, such as Google, Facebook, Twitter, etc.
  • CredentialsProvider 是我们将用于验证用户的提供商。这只使用用户名和密码。还有许多其他提供商可以使用,如 Google、Facebook、Twitter 等。
  • prisma is the Prisma client that we will use to query the database.
  • prisma 是 Prisma 客户端,我们将使用它来查询数据库。
  • PrismaAdapter is the Prisma adapter that we will use to integrate Next Auth with Prisma.
  • PrismaAdapter 是 Prisma 适配器,我们将使用它来集成 Next Auth 和 Prisma。

Create the Next Auth Configuration 创建 Next Auth 配置

Create a configuration object. We will add the different parts one-by-one so that you understand what each part does. 创建一个配置对象。我们将逐个添加不同的部分,以便你了解每个部分的作用。

Pages 页面

Add the following code to the auth.ts file: 将以下代码添加到 auth.ts 文件中:
This is the configuration for the pages that Next Auth will use. We are setting the sign-in page to /sign-in and the error page to /sign-in. This is because we want to redirect the user to the sign-in page if they are not authenticated. If you wanted to redirect the user to a different page, you could change the signIn and error properties to whatever page you want. 这是 Next Auth 将使用的页面配置。我们将登录页面设置为 /sign-in,错误页面也设置为 /sign-in。这是因为如果用户未通过身份验证,我们希望将他们重定向到登录页面。如果你想将用户重定向到不同的页面,可以将 signInerror 属性更改为你想要的任何页面。

Session 会话

The session object is used to describe how we want to save the user session. Add the following code to the auth.ts file just under the pages object, still within the config object: 会话对象用于描述我们如何保存用户会话。将以下代码添加到 auth.ts 文件中 pages 对象的正下方,仍在 config 对象内:
We are setting the strategy to jwt and the max age to 30 days. The max age is the amount of time that the session will be valid for. After this time, the user will have to log in again. 我们将策略设置为 jwt,将最大有效期设置为 30 天。最大有效期是会话有效的时间。过了这个时间,用户将不得不重新登录。

Adapter 适配器

We are using the Prisma adapter to integrate Next Auth with Prisma. Add the following code to the auth.ts file right under the session object: 我们使用 Prisma 适配器将 Next Auth 与 Prisma 集成。将以下代码添加到 auth.ts 文件中 session 对象的正下方:

Providers 提供商

The providers are the different ways that users can authenticate. We are using the CredentialsProvider which is a simple username and password. Add the following code to the auth.ts file right under the adapter object: 提供商是用户可以验证身份的不同方式。我们使用 CredentialsProvider,它是一个简单的用户名和密码。将以下代码添加到 auth.ts 文件中 adapter 对象的正下方:
We are saying we want to use the CredentialsProvider and we are setting the credentials to be an email and password. 我们表示要使用 CredentialsProvider,并将凭据设置为 emailpassword
The authorize function is called when the user tries to authenticate. This is where you handle email/password authentication and return the user object for the session if valid, otherwise return null. 当用户尝试验证身份时会调用 authorize 函数。这是你处理邮箱/密码验证的地方,如果有效则返回会话的用户对象,否则返回 null

Callbacks 回调

Lastly, we need to add the callbacks. Callbacks are functions that are called at different points in the authentication process. The session callback is called whenever a session is accessed or created. This is where you decide what user data is available to the client. 最后,我们需要添加回调。回调是在身份验证过程的不同点调用的函数。每当访问或创建会话时都会调用 session 回调。这是你决定哪些用户数据可供客户端使用的地方。
The session callback takes in a few parameters, including the session object, the user object, the trigger (which is the event that triggered the callback), and the token object. session 回调接受几个参数,包括 session 对象、user 对象、trigger(触发回调的事件)和 token 对象。
The first line of the callback sets the id property of the session.user object to the sub property or the subject of the token object. This is because the sub, property of the token object is the user's ID and is used to identify the user. 回调的第一行将 session.user 对象的 id 属性设置为 token 对象的 sub 属性或主题。这是因为 token 对象的 sub 属性是用户的 ID,用于识别用户。
The trigger argument allows you to detect why the session callback is being triggered. Here, it's checking if the callback is being triggered by an update. If trigger is 'update', it means the session has been updated, and you might want to modify the session accordingly. In this case, it's setting the name property of the session.user object to the name property of the user object. trigger 参数允许你检测为什么会触发会话回调。这里,它检查回调是否由更新触发。如果 trigger 是 'update',则表示会话已更新,你可能希望相应地修改会话。在这种情况下,它将 session.user 对象的 name 属性设置为 user 对象的 name 属性。
After updating the session object, the callback returns the updated session object, which may look something like this: 更新会话对象后,回调返回更新的会话对象,它可能看起来像这样:

jwt callback JWT 回调

It's important to know that a JSON Web Token is being sent to the client in the Authorization header. This token contains the user's ID by default. If you want to add more information to the token, you can do so in the jwt callback. We're going to do that later because right now we don't need anything else. Later though, we will want to return other user info like the role as well as the user's cart data. 重要的是要知道 JSON Web Token 是通过 Authorization header 发送给客户端的。默认情况下,此令牌包含用户的 ID。如果你想向令牌添加更多信息,可以在 jwt 回调中执行。我们稍后会这样做,因为目前我们不需要其他任何东西。不过稍后,我们将希望返回其他用户信息,如角色以及用户的购物车数据。
We are also going to add the following right after the config object: 我们还将在 config 对象之后立即添加以下内容:
If you aren't familiar with this Typescript syntax, satisfies ensures that the object structure (config) is compatible with the type (NextAuthConfig). If the object does not meet the requirements of the NextAuthConfig type, TypeScript will produce a type错误。It will also ensure type safety without forcing unnecessary type assertions. 如果你不熟悉这种 TypeScript 语法,satisfies 确保对象结构(config)与类型(NextAuthConfig)兼容。如果对象不符合 NextAuthConfig 类型的要求,TypeScript 将产生类型错误。它还将确保类型安全,而不会强制进行不必要的类型断言。
Now we just need to export: 现在我们只需要导出:
This will initialize NextAuth with the config object that we are providing it. It gives us access to the following, which we are exporting to use in our application: 这将使用我们提供的 config 对象初始化 NextAuth。它使我们能够访问以下内容,我们将导出这些内容以在我们的应用程序中使用:
  • handlers is an object that contains the HTTP handlers for the different endpoints that NextAuth uses. We will use these handlers to create the NextAuth API routes.
  • handlers 是一个对象,包含 NextAuth 使用的不同端点的 HTTP 处理程序。我们将使用这些处理程序来创建 NextAuth API 路由。
  • auth is a function that returns the current session. When we need to check if a user is authenticated and get the session, we will use this function.
  • auth 是一个返回当前会话的函数。当我们需要检查用户是否已通过身份验证并获取会话时,我们将使用此函数。
  • signIn is a function that signs in a user.
  • signIn 是一个登录用户的函数。
  • signOut is a function that signs out a user.
  • signOut 是一个退出用户的函数。
Here is the entire file: 这是整个文件:
Now we have to create the API route. 现在我们必须创建 API 路由。

5. Next Auth Catch All API Route NextAuth 捕获所有 API 路由

When we use NextAuth, it sets up a bunch of "hidden" API routes for us to handle things like signing in, signing out, and managing sessions. These routes are automatically created when you configure NextAuth and are used behind the scenes to handle authentication. We just need to create a single endpoint to hook those routes up. When we do this, we're saying, any route that starts with /api/auth/* will be controlled by NextAuth. Again, it's very opinionated. We added a bunch config and NextAuth looks at that and sets up everything for us. Creating the sessions, tokens, etc. 当我们使用 NextAuth 时,它会为我们设置一堆"隐藏"的 API 路由来处理登录、退出和管理会话等事情。这些路由在你配置 NextAuth 时自动创建,并在后台用于处理身份验证。我们只需要创建一个端点来连接这些路由。当我们这样做时,我们是在说,任何以 /api/auth/* 开头的路由都将由 NextAuth 控制。再次强调,它非常有主见。我们添加了一堆配置,NextAuth 会查看这些配置并为我们设置一切。创建会话、令牌等。
We also have access to all kinds of hooks and functions to do things like access the session object, sign in, sign out, etc. And what's great is we can add as many providers as we want and it works in the same way behind the scenes. 我们还可以访问各种钩子和函数来执行访问会话对象、登录、退出等操作。而且很棒的是,我们可以添加任意数量的提供商,它在后台以相同的方式工作。
So let's create our main route文件 and export the handlers. Create a new file in the app/api/auth folder called [...nextauth].ts. You can read more about how this works here. 因此,让我们创建我们的主路由文件并导出处理程序。在 app/api/auth 文件夹中创建一个名为 [...nextauth].ts 的新文件。你可以阅读更多关于这如何工作的内容这里
In the file, add the following code: 在文件中,添加以下代码:
In many projects, you will see all the stuff we added to the auth.js file directly in the [...nextauth].ts file. You could do that, but we are just exporting the handlers from the auth.js file. Either way, whenever a request is made to /api/auth/* it will be handled by Next Auth. 在许多项目中,你会看到我们添加到 auth.js 文件中的所有内容都直接在 [...nextauth].ts 文件中。你可以这样做,但我们只是从 auth.js 文件中导出处理程序。无论哪种方式,每当向 /api/auth/* 发出请求时,它都将由 Next Auth 处理。
There isn't a great way to test this yet because we have not created the sign in page yet. You could do the following for now: 目前还没有很好的方法来测试这个,因为我们还没有创建登录页面。你现在可以执行以下操作:
  • Go to the /api/auth/signin route in your browser. You should get a redirect to the callback URL
  • 在浏览器中访问 /api/auth/signin 路由。你应该会被重定向到回调 URL
  • Go to the /api/auth/signout route in your browser. You should see a messsage asking if you really want to sign out.
  • 在浏览器中访问 /api/auth/signout 路由。你应该会看到一条消息询问你是否真的要退出。
  • Go to the /api/auth/session route in your browser. You should see "null" because we have not created the sign in page yet to set the session but once you do, you should see the session data.
  • 在浏览器中访问 /api/auth/session 路由。你应该会看到 "null",因为我们还没有创建登录页面来设置会话,但一旦你这样做了,你应该会看到会话数据。
Now that we have our config and api routes, we can start to think about creating sign-in functionality. 现在我们有了配置和 API 路由,我们可以开始考虑创建登录功能了。
🗒️
上面是API Routes方式,如果是App Router,创建如下路径和文件 /app/api/auth/[...nextauth]/route.ts

6. Sign In & Sign Out Action 登录和退出操作

Now that we have Next Auth configured, we can create a sign in and sign out action. 现在我们已经配置好了 Next Auth,可以创建登录和退出操作了。

Create the Sign-In Form Schema 创建登录表单模式

Remember, we are using Zod for form and type validation. Open the lib/validator.ts file and add the following code: 记住,我们使用 Zod 进行表单和类型验证。打开 lib/validator.ts 文件并添加以下代码:
Pretty simple. We are just setting email and password to a string that must be at least 3 characters long. 非常简单。我们只是将邮箱和密码设置为必须至少 3 个字符的字符串。

Create The Actions 创建操作

Create a new file at lib/actions/user.actions.ts and add the following imports and mark as use server: 在 lib/actions/user.actions.ts 创建一个新文件并添加以下导入,并标记为 use server
The isRedirectError function is used to check if the error is a redirect error. We are also importing the signIn and signOut functions from the @/auth module. Finally, we are importing the signInFormSchema from the ../validator module that we created in the last lesson. isRedirectError 函数用于检查错误是否是重定向错误。我们还从 @/auth 模块导入 signInsignOut 函数。最后,我们从上一课创建的 ../validator 模块导入 signInFormSchema

Sign In 登录

Create a new function called signInWithCredentials that takes in the prevState and formData as parameters. This is the signature we need to use with the way we'll be submitting the form. Inside the function, we'll try to sign in the user with the credentials provided in the form data. If the sign-in is successful, we'll return a success message. If there is an error, we'll check if it is a "redirect error," which is thrown internally by NextAuth's redirect() function. If it's a redirect error, the function rethrows it so Next.js can handle the redirection. 创建一个名为 signInWithCredentials 的新函数,它接受 prevStateformData 作为参数。这是我们需要使用的签名,与我们提交表单的方式相匹配。在函数内部,我们将尝试使用表单数据中提供的凭据登录用户。如果登录成功,我们将返回成功消息。如果有错误,我们将检查它是否是"重定向错误",这是由 NextAuth 的 redirect() 函数在内部抛出的。如果它是重定向错误,函数会重新抛出它,以便 Next.js 可以处理重定向。

Sign Out 退出

The sign out is super simple. We already imported the signOut function from the @/auth module. So we can just call it. 退出非常简单。我们已经从 @/auth 模块导入了 signOut 函数。所以我们只需要调用它。
Add the following under the signInWithCredentials function: 在 signInWithCredentials 函数下方添加以下内容:
Here is the full code: 这是完整的代码:
Now we need to work on the page and form to sign in. 现在我们需要处理登录页面和表单。

7. Auth Layout & Sign In Page 认证布局和登录页面

In this lesson, we are going to create the layout and sign-in page. Auth pages will not have the header and menu. So we need a separate layout. 在本课中,我们将创建布局和登录页面。认证页面不会有页眉和菜单。所以我们需要一个单独的布局。
Create a new group folder in the app folder called (auth). We will have a separate layout for this group, so create /app/(auth)/layout.tsx and add the following code: 在 app 文件夹中创建一个名为 (auth) 的新组文件夹。我们将为这个组设置一个单独的布局,因此创建 /app/(auth)/layout.tsx 并添加以下代码:
Next, create a folder called sign-in and add a file called page.tsx and create a simple component for now: 接下来,创建一个名为 sign-in 的文件夹,添加一个名为 page.tsx 的文件,并暂时创建一个简单的组件:
Now you should be able to click on the sign in button in the header and go to /sign-in and see the text "Sign In". 现在你应该能够点击页眉中的登录按钮并转到 /sign-in 并看到文本 "Sign In"。
Let's add the imports that we will need: 让我们添加我们需要的导入:
Let's set a title. Below the imports and above the function, add the following: 让我们设置一个标题。在导入下方和函数上方,添加以下内容:
Now in the component, add the following: 现在在组件中,添加以下内容:
You should now see the card, logo, etc. Now we need to create the form, which will be in a new component. 你现在应该能看到卡片、标志等。现在我们需要创建表单,它将在一个新组件中。

8. Credentials Sign In Form 凭证登录表单

We now have our sign-in page. Now we need the form. We will have this in a separate page component called CredentialsSignInForm. The reason for this is that you may have other sign-in forms in the future, such as a sign-in form for a third-party service like Google or Facebook. By having a separate component, we can easily swap out the form without affecting the rest of the sign-in page.
我们现在已经有了登录页面。现在我们需要表单。我们将把它放在一个名为 CredentialsSignInForm 的独立页面组件中。这样做的原因是,将来您可能会有其他登录表单,例如 Google 或 Facebook 等第三方服务的登录表单。通过使用单独的组件,我们可以轻松地替换表单,而不会影响登录页面的其他部分。
Create a new component at app/(auth)/sign-in/credentials-signin-form.tsx and just add the following for now:
app/(auth)/sign-in/credentials-signin-form.tsx 创建一个新组件,暂时只添加以下内容:
Now bring it into the app/(auth)/sign-in/page.tsx file and add it to the page where it says "FORM HERE":
现在将其导入到 app/(auth)/sign-in/page.tsx 文件中,并将其添加到页面中标记为 "FORM HERE" 的位置:

Install Shadcn input & label components 安装 Shadcn 输入框和标签组件

Open a terminal and run the following command:
打开终端并运行以下命令:
Now add the following to the CredentialsSignInForm component:
现在将以下内容添加到 CredentialsSignInForm 组件中:
This is very simple so far. We are just displaying a form. We are bringing in the default values from the signInDefaultValues constant in the lib/constants.ts file.
到目前为止这非常简单。我们只是显示一个表单。我们从 lib/constants.ts 文件中的 signInDefaultValues 常量引入默认值。
lib/constants/index.ts
 
In the next lesson, we will hook up the form.
在下一课中,我们将连接表单。

9. Hook Up Sign In Form 连接登录表单

So we have our action and we have the form, now we need to connect them and make it work. Open the app/(auth)/sign-in/credentials-signin-form.tsx file and add a few more imports:
现在我们已经有了 action 和表单,接下来需要将它们连接起来使其正常工作。打开 app/(auth)/sign-in/credentials-signin-form.tsx 文件并添加几个导入:
We brought in the signInWithCredentials action, the useActionState from React and useFormStatus from react-dom to get the state and status of our form. We will use these to show the user feedback when they submit the form. One important thing I want to mention is that if you are using React 18 and Next.js 14, then you would use useFormState instead of useActionState:
我们引入了 signInWithCredentials action,来自 React 的 useActionState 和来自 react-domuseFormStatus,以获取表单的状态和提交状态。我们将使用这些来在用户提交表单时显示反馈。我想提到的一个重要点是,如果您使用的是 React 18 和 Next.js 14,那么您应该使用 useFormState 而不是 useActionState

Action State Action 状态

Add the following state to the component:
向组件添加以下状态:
This will give us the state/data of the action response, which remember, is an object with success and message. The useActionState hook takes in the action we want to call and the default state. The default state is an empty string for the message and a boolean for the success.
这将为我们提供 action 响应的状态/数据,请记住,这是一个包含 successmessage 的对象。useActionState hook 接收我们要调用的 action 和默认状态。默认状态中 message 是空字符串,success 是布尔值。
Now add the action to the form:
现在将 action 添加到表单中:

useFormStatus useFormStatus Hook

This hook allows us to get the status of the form. We will use this to show the user feedback when they submit the form. While the form is submitting, I want the submit button to say "Signing in...".
这个 hook 允许我们获取表单的状态。我们将使用它在用户提交表单时显示反馈。当表单正在提交时,我希望提交按钮显示 "Signing in..."。
Add the following code just above the return:
在 return 语句之前添加以下代码:
Now, replace the button in the form:
现在,替换表单中的按钮:

Respond To The Action 响应 Action

Now we need to show an error with a message if we get back an error from the action. Add the following code right under the closing </div> under the <SignInButton />:
现在我们需要在从 action 返回错误时显示错误消息。在 <SignInButton /> 下方的闭合 </div> 标签之后添加以下代码:
This will show the error message if there is an error.
如果有错误,这将显示错误消息。

Redirect To Homepage 重定向到首页

In the app/(auth)/sign-in/page.tsx, bring in redirect and the auth function, which is used to check for the session and if we are logged in.
app/(auth)/sign-in/page.tsx 中,引入 redirectauth 函数,该函数用于检查会话以及我们是否已登录。
In the SignInPage function, right above the return, add the following:
SignInPage 函数中,在 return 语句之前添加以下内容:
We are getting the session and redirecting if there is one.
我们正在获取会话,如果存在会话则进行重定向。

Test Sign In 测试登录

Now it's time for the moment of truth. Test the form out with an email and password that is wrong. You should see an error.
现在是关键时刻了。使用错误的电子邮件和密码测试表单。您应该会看到一个错误。
Now try with one of the users we seeded the database with such as admin@example.com and 123456.
现在尝试使用我们植入数据库的用户之一,例如 admin@example.com123456
If the login is successful, you should be redirected to the home page.
如果登录成功,您应该会被重定向到首页。
Open the devtools->application tab and you should have the session cookie.
打开开发者工具->应用程序选项卡,您应该能看到会话 cookie。
In the next video, we will add the callback URL redirect.
在下一个视频中,我们将添加回调 URL 重定向。

10. Callback URL Redirect 回调 URL 重定向

We also want to make it so that if we are at a certain page such as the cart, and then we sign it, we go back to that page. We can do this by adding a callback URL.
我们还希望实现这样的功能:当我们在某个页面(如购物车)时,登录后能够返回到该页面。我们可以通过添加回调 URL 来实现这一点。
Open the app/auth/sign-in/page.tsx and pass in the searchParams prop to get the callback URL:
打开 app/auth/sign-in/page.tsx 文件,传入 searchParams 属性来获取回调 URL:
Now, change the redirect to the following:
现在,将重定向代码修改为以下内容:
If there is a callback, it will redirect there first.
如果存在回调 URL,系统将优先重定向到该地址。
We also want to persist the callback when we submit the form. So we will just pass it as a hidden input in the form. In the app/(auth)/sign-in/sign-in-credentials.tsx add the following import:
我们还希望在提交表单时保持回调 URL。因此,我们将它作为隐藏输入字段传递给表单。在 app/(auth)/sign-in/sign-in-credentials.tsx 中添加以下导入:
This is how we get searchParams from a client component. We don't pass in props like the server component.
这是我们从客户端组件获取 searchParams 的方式。我们不像服务端组件那样通过 props 传递。
Now add the following below the action state:
现在在 action state 下方添加以下代码:
Now just pass it as a hidden input:
现在将它作为隐藏输入字段传递:
Now when we are on a page and sign in, we will be brought back to that page.
现在当我们在某个页面登录时,我们将会被带回到该页面。
You can test this by going to a URL like http://localhost:3000/sign-in?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fshipping-address and signing in and then you should be taken to the shipping-adress page. Which does not exist yet.
你可以通过访问类似 http://localhost:3000/sign-in?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fshipping-address 的 URL 来测试此功能,登录后你应该会被带到 shipping-address 页面。该页面目前还不存在。

11. User Button & Sign Out 用户按钮和退出登录

Now that the sign in is working, we need to change the header up a bit. We will add a user button component with the user initial and a dropdown with the sign out button.
现在登录功能已经正常工作,我们需要稍微修改一下头部组件。我们将添加一个用户按钮组件,显示用户名字首字母,以及一个包含退出登录按钮的下拉菜单。
Create a new file at components/shared/header/user-button.tsx and add the following code for now:
components/shared/header/user-button.tsx 创建一个新文件,暂时添加以下代码:
We are bringing in a bunch of UI components as well as the SignOutUser action from the user.actions file and the auth object from the auth.ts file.
我们引入了一系列 UI 组件,以及来自 user.actions 文件的 SignOutUser action 和来自 auth.ts 文件的 auth 对象。
Then we have an async function that will return a div with the text User Button. Make sure you make the function async.
然后我们有一个异步函数,它将返回一个包含文本 User Button 的 div。请确保将函数设为异步。
Let's bring it into the components/shared/header/menu.tsx file:
让我们将它引入到 components/shared/header/menu.tsx 文件中:
We want to put this in two places. In the regular menu and the sheet menu. Let's start with the regular menu. Replace the current sign in button with the user button:
我们希望将它放在两个地方:常规菜单和抽屉菜单。让我们从常规菜单开始。用用户按钮替换当前的登录按钮:
Now add it to the sheet menu right under the cart button:
现在将它添加到抽屉菜单中,放在购物车按钮下方:
Now you should see the text "User Button" in the header.
现在你应该能在头部看到 "User Button" 文本。
At the top of the function, we want to check for the session and show the sign in if there is not one:
在函数顶部,我们希望检查会话状态,如果没有会话则显示登录按钮:
You can log out by deleting the cookie and you should see the sign in button. Test it and then log back in.
你可以通过删除 cookie 来退出登录,然后你应该能看到登录按钮。测试一下,然后重新登录。
Now we need to show the user's initial and a dropdown with the sign out button.
现在我们需要显示用户名字的首字母,以及一个包含退出登录按钮的下拉菜单。
Add the following between the return statements to get the user name initial:
在 return 语句之间添加以下代码来获取用户名字的首字母:
Now let's add the output. Add the following to the return statement:
现在让我们添加输出内容。将以下内容添加到 return 语句中:
🗒️
当 DropdownMenuItem 包裹 form 时,点击 Sign Out 无法退出;去掉 DropdownMenuItem 后就可以正常退出。
这是因为在 Radix UI 的 DropdownMenuItem 组件中,点击事件会被拦截处理(用于菜单关闭等逻辑),导致内部的 form 提交无法正常工作。
You should now see the user initial and a dropdown with the sign out button.
现在你应该能看到用户名字的首字母,以及一个包含退出登录按钮的下拉菜单。
Test it out and make sure it works.
测试一下,确保它能正常工作。

12. Sign Up Form Schema & Action 注册表单 Schema 与操作

Now we will create the Zod schema for the sign up form data as well as the action to handle the form submission.
现在我们将创建用于注册表单数据的 Zod schema,以及处理表单提交的操作。

Sign Up Form Schema 注册表单 Schema

Open the file lib/validator.ts and add the following code:
打开文件 lib/validator.ts 并添加以下代码:
We are creating the schema for the sign up form data. There will be a name, email, password, and confirm password field. The name and email fields are required and must be at least 3 characters long. The password and confirm password fields are required and must be at least 3 characters long. We use the refine method on the object itself to check if the password and confirm password fields match. Zod's .refine() expects you to pass a function that returns true or false. If true, the validation succeeds and if false, the validation fails. If the validation fails, Zod adds an error to the field specified in the path (in this case, confirmPassword).
我们正在创建注册表单数据的 schema。表单将包含姓名、邮箱、密码和确认密码字段。姓名和邮箱字段是必填项,且至少需要 3 个字符。密码和确认密码字段也是必填项,且至少需要 3 个字符。我们在对象上使用 refine 方法来检查密码和确认密码字段是否匹配。Zod 的 .refine() 方法期望你传入一个返回 true 或 false 的函数。如果返回 true,验证通过;如果返回 false,验证失败。如果验证失败,Zod 会将错误添加到 path 中指定的字段(在本例中是 confirmPassword)。

Sign Up Action 注册操作

Let's create the action that will handle the sign up form submission. Open the file lib/actions/user.action.ts and add the following imports:
让我们创建处理注册表单提交的操作。打开文件 lib/actions/user.action.ts 并添加以下导入:
Here is the code for the action:
以下是操作的代码:
We are using the signUpFormSchema schema to validate the form data. We are using the hashSync function to hash the password. We are using the prisma.user.create method to create a new user. We are using the signIn function to sign in the user. We are returning the success status and a message.
我们使用 signUpFormSchema schema 来验证表单数据。我们使用 hashSync 函数对密码进行哈希处理。我们使用 prisma.user.create 方法创建新用户。我们使用 signIn 函数让用户登录。我们返回成功状态和消息。
As far as the error, it is very vauge. We can work on this later. First, I want to get the form working.
关于错误处理,目前还比较模糊。我们可以稍后改进。首先,我想让表单先运行起来。
In the next lesson, we will create the sign up form.
在下一课中,我们将创建注册表单。

13. Sign Up Page & Form 注册表单

We will now create the sign up page and form.
现在我们将创建注册页面和表单。
Create a new file at app/(auth)/sign-up/page.tsx. You can copy the code from the sign in form and edit it or just use this code:
app/(auth)/sign-up/page.tsx 创建一个新文件。你可以从登录表单复制代码并编辑,或者直接使用以下代码:
We are pretty much doing the same thing as the sign in page. We are getting the callback url from the search params and checking if the user is already signed in. If they are, we redirect them to the callback url or the home page.
我们基本上在做与登录页面相同的事情。我们从搜索参数中获取回调 URL,并检查用户是否已登录。如果已登录,我们将他们重定向到回调 URL 或首页。
Now let's create the form. Create a new file at app/(auth)/sign-up/signup-form.tsx. Again, you can copy from the credentials-signin-form.tsx and edit it or just use this code:
现在让我们创建表单。在 app/(auth)/sign-up/signup-form.tsx 创建一个新文件。同样,你可以从 credentials-signin-form.tsx 复制并编辑,或者直接使用以下代码:
This form is similar to the sign in form. We are using the useActionState hook to handle the form state and the useFormStatus hook to handle the form status. We are also using the useSearchParams hook to get the callback url from the search params.
这个表单与登录表单类似。我们使用 useActionState 钩子来处理表单状态,使用 useFormStatus 钩子来处理表单状态。我们还使用 useSearchParams 钩子从搜索参数中获取回调 URL。
We need to add the signUpDefaultValues constant to the lib/constants.ts file. Add this code to the file:
我们需要将 signUpDefaultValues 常量添加到 lib/constants.ts 文件中。将以下代码添加到文件中:
Fill it with what you want. I will add the following:
填入你想要的内容。我将添加以下内容:
Now let's add the sign up form to the sign up page. Open the app/(auth)/sign-up/page.tsx file and add the following code:
现在让我们将注册表单添加到注册页面。打开 app/(auth)/sign-up/page.tsx 文件并添加以下代码:
Test it with an email that is already taken such as 'admin@example.com'. You should see the the error 'Something Went Wrong'. Like I said, we will address this message soon.
使用已被占用的邮箱(如 'admin@example.com')进行测试。你应该看到错误 'Something Went Wrong'。就像我说的,我们很快会处理这个错误消息。
Now try with a new email. You should get registered and logged in.
现在尝试使用新邮箱。你应该能够注册并登录。

14. Sign Up Error Handling 注册错误处理

Our registration form is working, but we need to handle errors more gracefully. Let's go into the lib/actions/user.actions.ts file and do some experiments. In the catch block, add the following:
我们的注册表单已经可以工作了,但我们需要更优雅地处理错误。让我们进入 lib/actions/user.actions.ts 文件做一些实验。在 catch 块中,添加以下内容:
Now remove the required attribute from the name and email inputs and change the email type to "text" temporarily in the sign up form and try to register a user without a name and email. You will see something like this:
现在从姓名和邮箱输入框中移除 required 属性,并在注册表单中临时将邮箱类型改为 "text",然后尝试注册一个没有姓名和邮箱的用户。你会看到类似这样的输出:
Now, let's go back to the browser and try to register a user with an email that already exists and look in the server console/terminal. You will see something like this:
现在,让我们回到浏览器,尝试使用已存在的邮箱注册用户,并在服务器控制台/终端中查看。你会看到类似这样的输出:
This gives us some info we can use for an error handler.
这为我们提供了一些可以用于错误处理器的信息。
Let's create an error handler. Open the file lib/utils.ts and add the following:
让我们创建一个错误处理器。打开文件 lib/utils.ts 并添加以下内容:
🗒️
1. 对于 Prisma 错误(P2002): Prisma 5.x 配合 Driver Adapter (可能是 @prisma/adapter-neon 或类似的数据库适配器),它的 meta 结构和旧版完全不同。 const field = error.meta?.target ? error.meta.target[0] : 'Field'; 没有 target 属性,需要从 driverAdapterError 中提取字段 error.meta?.driverAdapterError?.cause?.constraint?.fields[0] 2. 对于 Zod 验证错误: error.errors 需要改成 error.issues
  1. 结论
你想获取
应该使用
Zod 验证错误的详细信息
error.issues
Prisma 错误的详细信息
error.meta
所有错误类型的通用消息
error.message
I used the "// eslint-disable-next-line @typescript-eslint/no-explicit-any" comment because I want to use the any type without any hassle from eslint. To include the types for this was way more complicated than I'd like.
我使用了 "// eslint-disable-next-line @typescript-eslint/no-explicit-any" 注释,因为我想使用 any 类型而不被 eslint 警告。为这种情况包含类型定义比我想要的要复杂得多。
Here, we are checking for two types of errors: ZodError and PrismaClientKnownRequestError. If the error is a ZodError, we format the error message by joining the error messages for each field. If the error is a PrismaClientKnownRequestError and the error code is P2002, we format the error message by capitalizing the first letter of the field name that caused the uniqueness error. If the error is neither a ZodError nor a PrismaClientKnownRequestError, we return the error message as a string.
在这里,我们检查两种类型的错误:ZodErrorPrismaClientKnownRequestError。如果是 ZodError,我们通过连接每个字段的错误消息来格式化错误信息。如果是 PrismaClientKnownRequestError 且错误代码是 P2002,我们通过将造成唯一性错误的字段名首字母大写来格式化错误消息。如果既不是 ZodError 也不是 PrismaClientKnownRequestError,我们将错误消息作为字符串返回。
Now bring it into the lib/actions/user.actions.ts file and update the catch block as follows:
现在将它引入到 lib/actions/user.actions.ts 文件中,并按如下方式更新 catch 块:
Now you should get a more user-friendly error message.
现在你应该会得到一个更加用户友好的错误消息。
You can put the required attribute back to the inputs.
你可以将 required 属性重新加回到输入框中。

15. Customizing the Token With The JWT Callback 使用JWT回调自定义令牌

So we have a working authentication system that sends a JWT token to the client. However, right now, that token contains just the name and email. I want to customize it to also have the role. We also want to check if the user has no name and use their email as their name if so. We also want to handle session updates such as a name change. To do this, we need to add a jwt callback.
我们已经有了一个可以向客户端发送JWT令牌的工作认证系统。然而,目前该令牌只包含姓名和邮箱。我想自定义它,让它也包含role(角色)字段。我们还想检查用户是否没有设置姓名,如果没有,就用邮箱作为姓名。我们还想处理会话更新,比如姓名更改。为此,我们需要添加一个jwt回调函数。

jwt callback JWT回调

Open the auth.ts file and add a jwt callback function.
打开auth.ts文件,添加一个jwt回调函数。
We are checking for the user and assigning the role to the token. We are also checking for the user's name and if it is NO_NAME, we are setting the name to the user's email address. We are also updating the user's name in the database. This is mainly for the magic link login we will implement later. Finally, we are checking if the session has a user name and if the trigger is update, we are setting the token's name to the session's user name.
我们在这里检查用户并将角色分配给令牌。我们还检查用户的姓名,如果是NO_NAME,就将姓名设置为用户的邮箱地址。我们还会更新数据库中的用户姓名。这主要是为了我们稍后将要实现的魔法链接登录。最后,我们检查会话是否有用户名,如果触发器是update,就将令牌的姓名设置为会话的用户名。

The session callback Session回调

The jwt callback runs before the session callback. Since we added a custom jwt callback, we need to manually assign the user's ID, name, and role to the session. So now, in the session callback, we need to add the following:
jwt回调在session回调之前运行。由于我们添加了自定义的jwt回调,我们需要手动将用户的ID、姓名和角色分配给会话。所以现在,在session回调中,我们需要添加以下内容:
We are mapping the token data to the session object. We are also checking if the trigger is update and if the token has a name, we are setting the session's user name to the token's name.
我们将令牌数据映射到会话对象。我们还检查触发器是否为update,以及令牌是否有姓名,如果有,就将会话的用户名设置为令牌的姓名。
If you log in and go to /api/auth/session, you should see something like this:
如果你登录并访问/api/auth/session,你应该会看到类似这样的内容:
Also, the JWT now includes the user's ID, name, and role so that we can access them on the client-side. If you try and paste the token in jwt.io, it will not show you the payload because it's encrypted. However, it is decrypted on the client-side in our app.
此外,JWT现在包含了用户的ID、姓名和角色,这样我们就可以在客户端访问这些信息。如果你尝试将令牌粘贴到jwt.io中,它不会显示有效载荷,因为它是加密的。不过,它会在我们的应用客户端被解密。
 

📎 参考文章

  • 一些引用
  • 引用文章
 
💡
欢迎您在底部评论区留言,一起交流~
上一篇
第一节 大脑:重新认识你自己
下一篇
Harness Engineering - 搭建Mini Harness
Loading...