(05)使用 Next 14 + NextAuth 4 + Strapi v4进行 Google 和凭据提供商身份验证的完整指南

文摘   2024-08-26 09:21   福建  

07.NextAuth 回调函数详解

本章的最终代码可以在 GitHub 上找到(分支:callbacksForGoogleProvider)。

https://github.com/peterlidee/NNAS/tree/callbacksForGoogleProvider

NextAuth 会在客户端(浏览器)中创建一个 JWT token 并将其设置在 cookie 中。除此之外,NextAuth 还允许我们通过客户端组件 useSession hook 或服务器组件 getServerSession 函数读取这个 token。

NextAuth 还提供了一些工具来定制我们在这个 token 中存储的内容以及从 session 中获取的数据。sessionuseSessiongetServerSession 返回的内容。这些工具被称为回调函数,我们在 authOptions 对象中定义它们。

概述

NextAuth 提供了四个回调函数:

  1. signIn
  2. redirect
  3. jwt
  4. session

jwt 回调函数负责将数据存储到 JWT token 中。NextAuth 默认会将一些数据放入 token 中。我们可以通过 jwt 回调函数自定义要存储到 token 中的其他数据。

正如你可能猜到的,session 回调函数处理的是放入 session 中的内容(即 useSessiongetServerSession 返回的内容)。我们同样可以自定义这些值。

我们不会使用其他两个回调函数。需要注意的是,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 属性,并包含 sessionjwt 回调函数:

// 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: {
    jwtasync function jwt({ token, user, account, profile, session, trigger }{
      // 处理一些事情
      return token;
    },
  }
}

我还必须回想一下回调函数实际上是做什么的。请看这个例子:

// 这部分是 NextAuth 正在做的事情
const myArr = [1234];
myArr.map(myCallback);

// 这将是我们在 `authOptions` 中的一个回调函数
function myCallback(item{
  console.log(item);
}

通过这个例子,你应该能够清楚地看到 jwtsession 回调函数中的参数是从哪里来的:NextAuth 调用回调时传递了这些参数,这就是为什么我们可以在函数体内访问这些参数的原因。

这只是一个旁注,让我们回到自定义上。请注意,我们将使用文档中那样的简写形式。

NextAuth jwt 回调函数

jwt 在每次创建 token(例如登录时)或更新或读取 token(例如使用 useSession)时都会被调用。这个回调用于在 NextAuth 创建的 JWT token 中添加额外的信息。它必须始终返回 token 参数。

这个回调函数接受很多参数,这让人感到困惑,但也有一些好消息:

  • 我们不需要所有这些参数,所以我们会忽略一些参数。耶!
  • 我们在使用 TypeScript,当我们将鼠标悬停在参数上时,可以看到它们的解释。

为了真正理解这些参数,将它们按顺序排列会有所帮助。除了 token 之外,这些参数通常是未定义的。只有在某些事件(例如登录或更新)发生时,它们才会填充数据。这很有意义,NextAuth 在登录时会发出身份验证请求。当用户已经登录时,没有必要调用提供商。

参数详解

  1. tokentoken 是将被放入 JWT token 中的内容。默认情况下,它包含 NextAuth 自动添加的值:

    {
      name'Peter Jacxsens',
      email: string,
      // 其他默认内容
    }

    在我们的情况下,我们会忽略除 nameemail 之外的所有内容。所以,这就是 NextAuth 默认放入我们 token 中的内容。

  2. triggertrigger 是像 NextAuth 事件一样的东西。当用户已经登录时,它是未定义的。我们稍后会用到它。

  3. accountaccount 包含提供商信息(例如 Google provider)在特定触发事件发生时的数据。例如,使用 Google provider 登录时,它看起来像这样:

    {
      provider'google',
      access_token: string,
      // 其他我们不需要的信息
    }
  4. profileprofile 是我们从提供商(例如 Google)返回的原始用户信息,它只在登录时填充,否则是未定义的。NextAuth 使用此信息创建一个 User。我们不会使用它,因此可以忽略它。

  5. useruserprofile 的精简版,它只在登录时填充。

    {
      id: string,
      name'Peter Jacxsens',
      email: string,
      image: string
    }
  6. session:我们将在处理 "update" 触发器时讨论这个参数。

回顾

我们快速回顾一下。我们正在讨论 jwt 回调函数。它会在特定事件(例如登录)发生时调用,也会在每次使用 useSessiongetServerSession 时调用。这个回调函数的主要目的是填充 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 }) {}。就这样。

总结

我们刚刚深入探讨了 jwtsession 回调函数的工作原理。在下一章中,我们将开始使用这些回调函数来解决本章开头提到的两个问题:

  1. 将用户信息保存到 Strapi 中:在用户通过 Google 登录时,我们需要将用户信息保存到 Strapi 数据库中。

  2. 将 Strapi 的 JWT Token 添加到 NextAuth 的 JWT Token 中:当我们在 Strapi 中创建了用户之后,需要将 Strapi 生成的 JWT Token 存储到 NextAuth 的 JWT Token 中,以便在需要时可以使用该 Token 进行进一步的 API 请求。

通过之前的理论讲解,你可能已经对如何实现这些目标有了模糊的想法。别担心,如果你忘记了这些参数的具体内容,没关系,在你实际自定义 token 或 session 时,你会逐步熟悉它们。

最后,我在 jwtsession 回调函数中添加了 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 中。


JavaScript 每日一练
每天一道JavaScript 实战题,让大家平时多多积累实用的知识,提高开发效率,才有更多的时间摸鱼。
 最新文章