|
|
# 新身份验证方式开发指南
|
|
|
|
|
|
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;
|
|
|
},
|
|
|
}
|
|
|
```
|