07.NextAuth 回调函数详解
本章的最终代码可以在 GitHub 上找到(分支:callbacksForGoogleProvider
)。
https://github.com/peterlidee/NNAS/tree/callbacksForGoogleProvider
NextAuth 会在客户端(浏览器)中创建一个 JWT token 并将其设置在 cookie 中。除此之外,NextAuth 还允许我们通过客户端组件 useSession
hook 或服务器组件 getServerSession
函数读取这个 token。
NextAuth 还提供了一些工具来定制我们在这个 token 中存储的内容以及从 session 中获取的数据。session
是 useSession
或 getServerSession
返回的内容。这些工具被称为回调函数,我们在 authOptions
对象中定义它们。
概述
NextAuth 提供了四个回调函数:
signIn redirect jwt session
jwt
回调函数负责将数据存储到 JWT token 中。NextAuth 默认会将一些数据放入 token 中。我们可以通过 jwt
回调函数自定义要存储到 token 中的其他数据。
正如你可能猜到的,session
回调函数处理的是放入 session 中的内容(即 useSession
或 getServerSession
返回的内容)。我们同样可以自定义这些值。
我们不会使用其他两个回调函数。需要注意的是,signIn
回调函数与我们之前用来重定向到登录页面或启动身份验证流程的 signIn
函数不同。signIn
回调的目的是控制用户是否被允许登录。
最后,redirect
回调允许我们自定义登录后的重定向行为。你可以在 NextAuth 文档中阅读更多关于这两个回调函数的内容。
Strapi
在开始编写代码之前,让我们先考虑需要完成的任务。我们需要实现两个目标。首先,在使用 Google provider 注册/登录的过程中,我们需要将用户信息存储到 Strapi 数据库中作为用户。
其次,当我们在 Strapi 中创建用户时,Strapi 会为该用户生成一个 JWT 访问 token。当我们请求非公开内容时,我们需要将这个访问 token 作为 header 发送给 Strapi 以便验证用户身份。只有经过身份验证的用户才能请求非公开内容。
我们应该将这个 Strapi token 存储在哪里呢?应该存储在 NextAuth 的 JWT token 中。所以,我们需要将 Strapi 的 JWT token 存储到 NextAuth 的 JWT token 中。这就是我们需要配置的内容。这也是我们第二个目标。
配置 authOptions
在深入探讨回调函数之前,我首先会在 authOptions
中添加一些其他设置:
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
// ...
session: {
strategy: 'jwt',
},
secret: process.env.NEXTAUTH_SECRET,
// ...
}
这些实际上只是默认设置,但我喜欢显式地声明它们。session
策略表示我们使用的是 JWT token。另一种选择是数据库策略,但那是另一个故事。secret
只是引用我们之前创建的 .env
文件。
回调函数的语法
在 authOptions
中添加 callback
属性,并包含 session
和 jwt
回调函数:
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
// ...
callbacks: {
async jwt({ token, trigger, account, profile, user, session }) {
// 处理一些事情
return token;
},
async session({ token, user, session, newSession, trigger }) {
// 处理一些事情
return session;
},
},
// ...
}
这是文档中的语法,最初让我感到困惑。我们是在调用这个函数吗?但是为什么它有函数体呢?这让我感到困惑。通过写出长版本,我设法理解了发生了什么:
{
callbacks: {
jwt: async function jwt({ token, user, account, profile, session, trigger }) {
// 处理一些事情
return token;
},
}
}
我还必须回想一下回调函数实际上是做什么的。请看这个例子:
// 这部分是 NextAuth 正在做的事情
const myArr = [1, 2, 3, 4];
myArr.map(myCallback);
// 这将是我们在 `authOptions` 中的一个回调函数
function myCallback(item) {
console.log(item);
}
通过这个例子,你应该能够清楚地看到 jwt
和 session
回调函数中的参数是从哪里来的:NextAuth 调用回调时传递了这些参数,这就是为什么我们可以在函数体内访问这些参数的原因。
这只是一个旁注,让我们回到自定义上。请注意,我们将使用文档中那样的简写形式。
NextAuth jwt
回调函数
jwt
在每次创建 token(例如登录时)或更新或读取 token(例如使用 useSession
)时都会被调用。这个回调用于在 NextAuth 创建的 JWT token 中添加额外的信息。它必须始终返回 token
参数。
这个回调函数接受很多参数,这让人感到困惑,但也有一些好消息:
我们不需要所有这些参数,所以我们会忽略一些参数。耶! 我们在使用 TypeScript,当我们将鼠标悬停在参数上时,可以看到它们的解释。
为了真正理解这些参数,将它们按顺序排列会有所帮助。除了 token
之外,这些参数通常是未定义的。只有在某些事件(例如登录或更新)发生时,它们才会填充数据。这很有意义,NextAuth 在登录时会发出身份验证请求。当用户已经登录时,没有必要调用提供商。
参数详解
token:
token
是将被放入 JWT token 中的内容。默认情况下,它包含 NextAuth 自动添加的值:{
name: 'Peter Jacxsens',
email: string,
// 其他默认内容
}在我们的情况下,我们会忽略除
name
和email
之外的所有内容。所以,这就是 NextAuth 默认放入我们 token 中的内容。trigger:
trigger
是像 NextAuth 事件一样的东西。当用户已经登录时,它是未定义的。我们稍后会用到它。account:
account
包含提供商信息(例如 Google provider)在特定触发事件发生时的数据。例如,使用 Google provider 登录时,它看起来像这样:{
provider: 'google',
access_token: string,
// 其他我们不需要的信息
}profile:
profile
是我们从提供商(例如 Google)返回的原始用户信息,它只在登录时填充,否则是未定义的。NextAuth 使用此信息创建一个User
。我们不会使用它,因此可以忽略它。user:
user
是profile
的精简版,它只在登录时填充。{
id: string,
name: 'Peter Jacxsens',
email: string,
image: string
}session:我们将在处理 "update" 触发器时讨论这个参数。
回顾
我们快速回顾一下。我们正在讨论 jwt
回调函数。它会在特定事件(例如登录)发生时调用,也会在每次使用 useSession
或 getServerSession
时调用。这个回调函数的主要目的是填充 NextAuth 的 JWT token。它是一个回调函数,可以让我们访问一系列参数:token
始终是已填充的。在某些触发事件时,其他参数将被填充。我们可以监听这些触发事件,然后有条件地自定义 token。
NextAuth session
回调函数
接下来是我们的第二个回调函数:session
。这个回调函数的目标很简单,它用于填充 useSession
hook 和 getServerSession
函数返回的内容。因此,我们使用 session
回调函数自定义我们的 NextAuth session。以下是我们的回调函数。请注意,我们必须始终从这个回调中返回 session
:
async session({ token, user, session, newSession, trigger }) {
// 处理一些事情
return session;
},
与 jwt
回调函数类似,token
参数始终是已填充的。实际上,这里的 token
等于 jwt
回调函数的返回值。jwt
先被调用,然后是 session
回调。以下是 token
参数的样子:
{
name: 'Peter Jacxsens',
email: string,
// 其他我们忽略的内容
}
在 session
回调中,session
参数同样始终是已填充的,无论我们是已登录还是正在登录。session
参数的样子如下:
{
user: {
name: 'Peter Jacxsens',
email: string,
image: string,
},
expires: Date
}
我们之前在 console.log 输出 useSession 或 getServerSession 时已经见过这个结构。这是 NextAuth 默认放入我们 session 的内容,使用 Google provider 时就是这样的。
session 回调的其他参数:user 和 newSession 只有在 authOptions 的 session.strategy 为 database 时才可用(我们使用 session.strategy 为 jwt)。因此,我们将忽略它们。我们也不需要 trigger 参数,所以也会忽略它。
因此,下次我们使用 session 回调时,它看起来会像这样:async session({ token, session }) {}
。就这样。
总结
我们刚刚深入探讨了 jwt
和 session
回调函数的工作原理。在下一章中,我们将开始使用这些回调函数来解决本章开头提到的两个问题:
将用户信息保存到 Strapi 中:在用户通过 Google 登录时,我们需要将用户信息保存到 Strapi 数据库中。
将 Strapi 的 JWT Token 添加到 NextAuth 的 JWT Token 中:当我们在 Strapi 中创建了用户之后,需要将 Strapi 生成的 JWT Token 存储到 NextAuth 的 JWT Token 中,以便在需要时可以使用该 Token 进行进一步的 API 请求。
通过之前的理论讲解,你可能已经对如何实现这些目标有了模糊的想法。别担心,如果你忘记了这些参数的具体内容,没关系,在你实际自定义 token 或 session 时,你会逐步熟悉它们。
最后,我在 jwt
和 session
回调函数中添加了 console.log
语句,这样你可以轻松检查日志,了解各个参数的具体内容。这些日志信息会让你的终端显示大量内容,所以在调试完成后可以将它们注释掉。
以下是更新后的代码:
// frontend/src/api/auth/[...nextAuth]/authOptions.ts
{
callbacks: {
async jwt({ token, trigger, profile, user, session }) {
console.log('jwt callback', {
token,
trigger,
profile,
user,
session,
});
return token;
},
async session({ token, session }) {
console.log('session callback', {
token,
session,
});
return session;
},
},
}
我们将在下一章继续讨论,具体实现如何将用户信息保存到 Strapi,并将 Strapi 的 JWT Token 集成到 NextAuth 的 JWT Token 中。