8000 🐛 fix: fix OAuth don't get user id from session (#1347) · lobehub/lobe-chat@ce4d6ca · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit ce4d6ca

Browse files
authored
🐛 fix: fix OAuth don't get user id from session (#1347)
* ⬆️ chore: update next-auth to 5.0.0-beta.13 - ⬆️ chore: update @auth/core to ^0.27.0 * 🐛 fix: Could not get `user_id` from session * ⬆️ chore: Unlock next-auth to `beta` * 📝 docs: Add custom session guide * 🐛 fix: use `user_id` from next-auth
1 parent 6485db7 commit ce4d6ca

File tree

4 files changed

+247
-2
lines changed

4 files changed

+247
-2
lines changed

contributing/Basic/Add-New-Authentication-Providers.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ LobeChat uses [Auth.js v5](https://authjs.dev/) as the external authentication s
1010
- [Step 2: Update Server Configuration Code](#step-2-update-server-configuration-code)
1111
- [Step 3: Change Frontend Pages](#step-3-change-frontend-pages)
1212
- [Step 4: Configure the Environment Variables](#step-4-configure-the-environment-variables)
13+
- [Step 5: Modify server-side user information processing logic](#step-5-modify-server-side-user-information-processing-logic)
1314

1415
## Add New Authentication Provider
1516

@@ -82,4 +83,111 @@ This value is the id of the Auth.js provider, and you can read the source code o
8283

8384
Add `OKTA_CLIENT_ID``OKTA_CLIENT_SECRET``OKTA_ISSUER` environment variables when you deploy.
8485

86+
### Step 5: Modify server-side user information processing logic
87+
88+
#### Get user information in the frontend
89+
90+
Use the `useOAuthSession()` method in the frontend page to get the user information `user` returned by the backend:
91+
92+
```ts
93+
import { useOAuthSession } from '@/hooks/useOAuthSession';
94+
95+
const { user, isOAuthLoggedIn } = useOAuthSession();
96+
```
97+
98+
The default type of `user` is `User`, and the type definition is:
99+
100+
```ts
101+
interface User {
102+
id?: string;
103+
name?: string | null;
104+
email?: string | null;
105+
image?: string | null;
106+
}
107+
```
108+
109+
#### Modify user `id` handling logic
110+
111+
The `user.id` is used to identify users. When introducing a new OAuth identity provider, you need to handle the information carried in the OAuth callback in `src/app/api/auth/next-auth.ts`. You need to select the user's `id` from this information. Before that, we need to understand the data processing sequence of `Auth.js`:
112+
113+
```txt
114+
authorize --> jwt --> session
115+
```
116+
117+
By default, in the `jwt --> session` process, `Auth.js` will [automatically assign the user `id` to `account.providerAccountId` based on the login type](https://authjs.dev/reference/core/types#provideraccountid). If you need to select a different value as the user `id`, you need to implement the following handling logic:
118+
119+
```ts
120+
callbacks: {
121+
async jwt({ token, account, profile }) {
122+
if (account) {
123+
// You can select a different value from `account` or `profile`
124+
token.userId = account.providerAccountId;
125+
}
126+
return token;
127+
},
128+
},
129+
```
130+
131+
#### Customize `session` return
132+
133+
If you want to carry more information about `profile` and `account` in the `session`, according to the data processing order mentioned above in `Auth.js`, you must first copy this information to the `token`. For example, add the user avatar URL `profile.picture` to the `session`:
134+
135+
```diff
136+
callbacks: {
137+
async jwt({ token, profile, account }) {
138+
if (profile && account) {
139+
token.userId = account.providerAccountId;
140+
+ token.avatar = profile.picture;
141+
}
142+
return token;
143+
},
144+
async session({ session, token }) {
145+
if (session.user) {
146+
session.user.id = token.userId ?? session.user.id;
147+
+ session.user.avatar = token.avatar;
148+
}
149+
return session;
150+
},
151+ 23D3
},
152+
```
153+
154+
Then supplement the type definition for the new parameters:
155+
156+
```ts
157+
declare module '@auth/core/jwt' {
158+
interface JWT {
159+
// ...
160+
avatar?: string;
161+
}
162+
}
163+
164+
declare module 'next-auth' {
165+
interface User {
166+
avatar?: string;
167+
}
168+
}
169+
```
170+
171+
> [More built-in type extensions in Auth.js](https://authjs.dev/getting-started/typescript#module-augmentation)
172+
173+
#### Differentiate multiple authentication providers in the processing logic
174+
175+
If you have configured multiple authentication providers and their `userId` mappings are different, you can use the `account.provider` parameter in the `jwt` method to get the default id of the identity provider and enter different processing logic.
176+
177+
```ts
178+
callbacks: {
179+
async jwt({ token, profile, account }) {
180+
if (profile && account) {
181+
if (account.provider === 'authing')
182+
token.userId = account.providerAccountId ?? token.sub;
183+
else if (acount.provider === 'auth0')
184+
token.userId = profile.sub ?? token.sub;
185+
else
186+
// other providers
187+
}
188+
return token;
189+
},
190+
}
191+
```
192+
85193
Now, you can use Okta as your provider to implement the authentication feature in LobeChat.

contributing/Basic/Add-New-Authentication-Providers.zh-CN.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ LobeChat 使用 [Auth.js v5](https://authjs.dev/) 作为外部身份验证服务
1010
- [步骤 2: 更新服务端配置代码](#步骤-2-更新服务端配置代码)
1111
- [步骤 3: 修改前端页面](#步骤-3-修改前端页面)
1212
- [步骤 4: 配置环境变量](#步骤-4-配置环境变量)
13+
- [步骤 5: 修改服务端用户信息处理逻辑](#步骤-5-修改服务端用户信息处理逻辑)
1314

1415
## 添加新的身份验证提供者
1516

@@ -81,3 +82,111 @@ export const getAppConfig = () => {
8182
### 步骤 4: 配置环境变量
8283

8384
在部署时新增 Okta 相关的环境变量 `OKTA_CLIENT_ID``OKTA_CLIENT_SECRET``OKTA_ISSUER`,并填入相应的值,即可使用
85+
86+
### 步骤 5: 修改服务端用户信息处理逻辑
87+
88+
#### 在前端获取用户信息
89+
90+
在前端页面中使用 `useOAuthSession()` 方法获取后端返回的用户信息 `user`
91+
92+
```ts
93+
import { useOAuthSession } from '@/hooks/useOAuthSession';
94+
95+
const { user, isOAuthLoggedIn } = useOAuthSession();
96+
```
97+
98+
默认的 `user` 类型为 `User`,类型定义为:
99+
100+
```ts
101+
interface User {
102+
id?: string;
103+
name?: string | null;
104+
email?: string | null;
105+
image?: string | null;
106+
}
107+
```
108+
109+
#### 修改用户 `id` 处理逻辑
110+
111+
`user.id` 用于标识用户。当引入新身份 OAuth 提供者后,您需要在 `src/app/api/auth/next-auth.ts` 中处理 OAuth 回调所携带的信息。您需要从中选取用户的 `id`。在此之前,我们需要了解 `Auth.js` 的数据处理顺序:
112+
113+
```txt
114+
authorize --> jwt --> session
115+
```
116+
117+
默认情况下,在 `jwt --> session` 过程中,`Auth.js`[自动根据登陆类型](https://authjs.dev/reference/core/types#provideraccountid)将用户 `id` 赋值到 `account.providerAccountId` 中。 如果您需要选取其他值作为用户 `id` ,您需要实现以下处理逻辑。
118+
119+
```ts
120+
callbacks: {
121+
async jwt({ token, account, profile }) {
122+
if (account) {
123+
// 您可以从 `account` 或 `profile` 中选取其他值
124+
token.userId = account.providerAccountId;
125+
}
126+
return token;
127+
},
128+
},
129+
```
130+
131+
#### 自定义 `session` 返回
132+
133+
如果您想在 `session` 中携带更多关于 `profile``account` 的信息,根据上面提到的 `Auth.js` 数据处理顺序,那必须先将该信息复制到 `token` 上。
134+
示例:把用户头像 URL:`profile.picture` 添加到`session` 中:
135+
136+
```diff
137+
callbacks: {
138+
async jwt({ token, profile, account }) {
139+
if (profile && account) {
140+
token.userId = account.providerAccountId;
141+
+ token.avatar = profile.picture;
142+
}
143+
return token;
144+
},
145+
async session({ session, token }) {
146+
if (session.user) {
147+
session.user.id = token.userId ?? session.user.id;
148+
+ session.user.avatar = token.avatar;
149+
}
150+
return session;
151+
},
152+
},
153+
```
154+
155+
然后补充对新增参数的类型定义:
156+
157+
```ts
158+
declare module '@auth/core/jwt' {
159+
interface JWT {
160+
// ...
161+
avatar?: string;
162+
}
163+
}
164+
165+
declare module 'next-auth' {
166+
interface User {
167+
avatar?: string;
168+
}
169+
}
170+
```
171+
172+
> [更多`Auth.js`内置类型拓展](https://authjs.dev/getting-started/typescript#module-augmentation)
173+
174+
#### 在处理逻辑中区分多个身份验证提供者
175+
176+
如果您配置了多个身份验证提供者,并且他们的 `userId` 映射各不相同,可以在 `jwt` 方法中的 `account.provider` 参数获取身份提供者的默认 id ,从而进入不同的处理逻辑。
177+
178+
```ts
179+
callbacks: {
180+
async jwt({ token, profile, account }) {
181+
if (profile && account) {
182+
if (account.provider === 'Authing')
183+
token.userId = account.providerAccountId ?? token.sub;
184+
else if (acount.provider === 'Okta')
185+
token.userId = profile.sub ?? token.sub;
186+
else
187+
// other providers
188+
}
189+
return token;
190+
},
191+
}
192+
```

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
},
7575
"dependencies": {
7676
"@ant-design/icons": "^5",
77-
"@auth/core": "^0.26.3",
77+
"@auth/core": "^0.27.0",
7878
"@aws-sdk/client-bedrock-runtime": "^3.503.1",
7979
"@azure/openai": "^1.0.0-beta.11",
8080
"@cfworker/json-schema": "^1",
@@ -107,7 +107,7 @@
107107
"modern-screenshot": "^4",
108108
"nanoid": "^5",
109109
"next": "^14.1",
110-
"next-auth": "5.0.0-beta.11",
110+
"next-auth": "beta",
111111
"next-sitemap": "^4.2.3",
112112
"numeral": "^2.0.6",
113113
"nuqs": "^1.15.4",

src/app/api/auth/next-auth.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,38 @@ import { getServerConfig } from '@/config/server';
66
const { AUTH0_CLIENT_ID, ENABLE_OAUTH_SSO, AUTH0_CLIENT_SECRET, AUTH0_ISSUER, NEXTAUTH_SECRET } =
77
getServerConfig();
88

9+
declare module '@auth/core/jwt' {
10+
// Returned by the `jwt` callback and `auth`, when using JWT sessions
11+
interface JWT {
12+
userId?: string;
13+
}
14+
}
15+
916
const nextAuth = NextAuth({
17+
callbacks: {
18+
// Note: Data processing order of callback: authorize --> jwt --> session
19+
async jwt({ token, account }) {
20+
// Auth.js will process the `providerAccountId` automatically
21+
// ref: https://authjs.dev/reference/core/types#provideraccountid
22+
if (account) {
23+
token.userId = account.providerAccountId;
24+
}
25+
return token;
26+
},
27+
async session({ session, token }) {
28+
// Pick userid from token
29+
if (session.user) {
30+
session.user.id = token.userId ?? session.user.id;
31+
}
32+
return session;
33+
},
34+
},
1035
providers: ENABLE_OAUTH_SSO
1136
? [
1237
Auth0({
38+
// Specify auth scope, at least include 'openid email'
39+
// all scopes in Auth0 ref: https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims
40+
authorization: { params: { scope: 'openid email profile' } },
1341
clientId: AUTH0_CLIENT_ID,
1442
clientSecret: AUTH0_CLIENT_SECRET,
1543
issuer: AUTH0_ISSUER,

0 commit comments

Comments
 (0)
0