# 新身份验证方式开发指南 LobeChat 使用 [Auth.js v5](https://authjs.dev/) 作为外部身份验证服务。Auth.js 是一个开源的身份验证库,它提供了一种简单的方式来实现身份验证和授权功能。本文档将介绍如何使用 Auth.js 来实现新的身份验证方式。 ### TOC - [添加新的身份验证提供者](#添加新的身份验证提供者) - [准备工作:查阅官方的提供者列表](#准备工作查阅官方的提供者列表) - [步骤 1: 新增关键代码](#步骤-1-新增关键代码) - [步骤 2: 更新服务端配置代码](#步骤-2-更新服务端配置代码) - [步骤 3: 修改前端页面](#步骤-3-修改前端页面) - [步骤 4: 配置环境变量](#步骤-4-配置环境变量) - [步骤 5: 修改服务端用户信息处理逻辑](#步骤-5-修改服务端用户信息处理逻辑) ## 添加新的身份验证提供者 为了在 LobeChat 中添加新的身份验证提供者(例如添加 Okta),你需要完成以下步骤: ### 准备工作:查阅官方的提供者列表 首先,你需要查阅 [Auth.js 提供者列表](https://authjs.dev/reference/core/providers) 来了解是否你的提供者已经被支持。如果你的提供者已经被支持,你可以直接使用 Auth.js 提供的 SDK 来实现身份验证功能。 接下来我会以 [Okta](https://authjs.dev/reference/core/providers/okta) 为例来介绍如何添加新的身份验证提供者 ### 步骤 1: 新增关键代码 打开 `src/app/api/auth/next-auth.ts` 文件,引入 `next-auth/providers/okta` ```ts import { NextAuth } from 'next-auth'; import Auth0 from 'next-auth/providers/auth0'; import Okta from 'next-auth/providers/okta'; // 引入 Okta 提供者 ``` 新增预定义的服务端配置 ```ts // 导入服务器配置 const { OKTA_CLIENT_ID, OKTA_CLIENT_SECRET, OKTA_ISSUER } = getServerConfig(); const nextAuth = NextAuth({ providers: [ // ... 其他提供者 Okta({ clientId: OKTA_CLIENT_ID, clientSecret: OKTA_CLIENT_SECRET, issuer: OKTA_ISSUER, }), ], }); ``` ### 步骤 2: 更新服务端配置代码 打开 `src/config/server/app.ts` 文件,在 `getAppConfig` 函数中新增 Okta 相关的环境变量 ```ts export const getAppConfig = () => { // ... 其他代码 return { // ... 其他环境变量 OKTA_CLIENT_ID: process.env.OKTA_CLIENT_ID || '', OKTA_CLIENT_SECRET: process.env.OKTA_CLIENT_SECRET || '', OKTA_ISSUER: process.env.OKTA_ISSUER || '', }; }; ``` ### 步骤 3: 修改前端页面 修改在 `src/features/Conversation/Error/OAuthForm.tsx` 及 `src/app/settings/common/Common.tsx` 中的 `signIn` 函数参数 默认为 `auth0`,你可以将其修改为 `okta` 以切换到 Okta 提供者,或删除该参数以支持所有已添加的身份验证服务 该值为 Auth.js 提供者 的 id,你可以阅读相应的 `next-auth/providers` 模块源码以读取默认 ID ### 步骤 4: 配置环境变量 在部署时新增 Okta 相关的环境变量 `OKTA_CLIENT_ID`、`OKTA_CLIENT_SECRET`、`OKTA_ISSUER`,并填入相应的值,即可使用 ### 步骤 5: 修改服务端用户信息处理逻辑 #### 在前端获取用户信息 在前端页面中使用 `useOAuthSession()` 方法获取后端返回的用户信息 `user`: ```ts import { useOAuthSession } from '@/hooks/useOAuthSession'; const { user, isOAuthLoggedIn } = useOAuthSession(); ``` 默认的 `user` 类型为 `User`,类型定义为: ```ts interface User { id?: string; name?: string | null; email?: string | null; image?: string | null; } ``` #### 修改用户 `id` 处理逻辑 `user.id` 用于标识用户。当引入新身份 OAuth 提供者后,您需要在 `src/app/api/auth/next-auth.ts` 中处理 OAuth 回调所携带的信息。您需要从中选取用户的 `id`。在此之前,我们需要了解 `Auth.js` 的数据处理顺序: ```txt authorize --> jwt --> session ``` 默认情况下,在 `jwt --> session` 过程中,`Auth.js` 会[自动根据登陆类型](https://authjs.dev/reference/core/types#provideraccountid)将用户 `id` 赋值到 `account.providerAccountId` 中。 如果您需要选取其他值作为用户 `id` ,您需要实现以下处理逻辑。 ```ts callbacks: { async jwt({ token, account, profile }) { if (account) { // 您可以从 `account` 或 `profile` 中选取其他值 token.userId = account.providerAccountId; } return token; }, }, ``` #### 自定义 `session` 返回 如果您想在 `session` 中携带更多关于 `profile` 及 `account` 的信息,根据上面提到的 `Auth.js` 数据处理顺序,那必须先将该信息复制到 `token` 上。 示例:把用户头像 URL:`profile.picture` 添加到`session` 中: ```diff callbacks: { async jwt({ token, profile, account }) { if (profile && account) { token.userId = account.providerAccountId; + token.avatar = profile.picture; } return token; }, async session({ session, token }) { if (session.user) { session.user.id = token.userId ?? session.user.id; + session.user.avatar = token.avatar; } return session; }, }, ``` 然后补充对新增参数的类型定义: ```ts declare module '@auth/core/jwt' { interface JWT { // ... avatar?: string; } } declare module 'next-auth' { interface User { avatar?: string; } } ``` > [更多`Auth.js`内置类型拓展](https://authjs.dev/getting-started/typescript#module-augmentation) #### 在处理逻辑中区分多个身份验证提供者 如果您配置了多个身份验证提供者,并且他们的 `userId` 映射各不相同,可以在 `jwt` 方法中的 `account.provider` 参数获取身份提供者的默认 id ,从而进入不同的处理逻辑。 ```ts callbacks: { async jwt({ token, profile, account }) { if (profile && account) { if (account.provider === 'Authing') token.userId = account.providerAccountId ?? token.sub; else if (acount.provider === 'Okta') token.userId = profile.sub ?? token.sub; else // other providers } return token; }, } ```