NextJS集成NextAuth时JWT回调报错问题求助
问题:NextAuth JWT回调报错 "JWT Claims Set MUST be an object"
问题场景
我在NextJS中使用自定义Token服务实现NextAuth认证,核心代码如下:
async function refreshAccessToken(authToken: AuthToken) { try { const tokenResponse = await AuthApi.refreshToken(authToken.refresh_token); return new AuthToken( tokenResponse.access_token, tokenResponse.token_type, tokenResponse.expires_in, tokenResponse.refresh_token ); } catch (error) { return new AuthToken( authToken?.access_token, authToken?.token_type, authToken?.expires_in, authToken?.refresh_token, "An error occurred whilst refreshing token" ); } } export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: 'credentials', credentials: { username: { label: 'Username', type: 'text' }, password: { label: 'Password', type: 'password' } }, authorize: async (credentials) => { try { if (!credentials) { return null; } const { username, password } = credentials; const authToken = await AuthApi.login(username, password); const session: Session = new Session(authToken, null); if (authToken != null && authToken.error == '') { const userDetails = await AuthApi.getUserDetails(authToken.access_token); if (userDetails != null) { session.user = userDetails; } } return session as any; } catch (error) { console.error(error); throw error; } } }) ], session: { strategy: 'jwt', maxAge: Constants.AccessTokenLifetimeSeconds }, secret: process.env.APP_SECRET, jwt: { secret: process.env.APP_SECRET, maxAge: Constants.AccessTokenLifetimeSeconds }, pages: { signIn: '/login' }, callbacks: { jwt: async ({ user }: any) => { let token: AuthToken | null = null; if (user != null && user.token != null) { token = user.token; const clock = new Clock(); if (user.token.expiry_date_time < clock.nowUtc()) { token = await refreshAccessToken(user.token); } } return token; }, session: async ({ session, user, token }) => { return session; } } }; export default NextAuth(authOptions);
相关类定义:
export default class AuthToken implements IAuthToken { public expiry_date_time: Date; constructor( public access_token: string, public token_type: string, public expires_in: number, public refresh_token: string, public error: string = "" ) { const clock = new Clock(); this.expiry_date_time = new Date( clock.nowUtc().getTime() + (this.expires_in - 60) * 1000 ); } }
export default class Session implements ISession { constructor( public token: AuthToken | null, public user: UserDetails | null ) { } }
报错信息
error - TypeError: JWT Claims Set MUST be an object at new ProduceJWT (C:\VRPM\Repos\portal\node_modules\jose\dist\node\cjs\jwt\produce.js:10:19) at new EncryptJWT (C:\VRPM\Repos\portal\node_modules\jose\dist\node\cjs\jwt\encrypt.js:7:1) at Object.encode (C:\VRPM\Repos\portal\node_modules\next-auth\jwt\index.js:49:16) at async Object.callback (C:\VRPM\Repos\portal\node_modules\next-auth\core\routes\callback.js:429:22) at async NextAuthHandler (C:\VRPM\Repos\portal\node_modules\next-auth\core\index.js:295:28) at async NextAuthNextHandler (C:\VRPM\Repos\portal\node_modules\next-auth\next\index.js:23:19) at async C:\VRPM\Repos\portal\node_modules\next-auth\next\index.js:59:32 at async Object.apiResolver (C:\VRPM\Repos\portal\node_modules\next\dist\server\api-utils\node.js:184:9) at async DevServer.runApi (C:\VRPM\Repos\portal\node_modules\next\dist\server\next-server.js:403:9) at async Object.fn (C:\VRPM\Repos\portal\node_modules\next\dist\server\base-server.js:493:37) { page: '/api/auth/[...nextauth]'
我返回的AuthToken明明是对象(控制台输出如下),但仍报错:
token: AuthToken { access_token: '...Nn_ooqUtFvdfh53k', token_type: 'Bearer', expires_in: 86400, refresh_token: '...ZddCpNTc', error: '', expiry_date_time: 2023-02-09T19:29:15.307Z }
我发现返回return { token: token };时错误消失,但想了解正确的处理方式是什么?
问题原因
NextAuth的jwt回调要求返回值必须是普通JavaScript对象(Plain Object),而你返回的是自定义AuthToken类的实例。类实例包含原型链信息,并非纯对象,底层依赖的jose库无法将其正确序列化为JWT的Claims Set,因此抛出错误。
正确处理方式
1. 将类实例转换为普通对象
在返回前,把AuthToken实例转为纯对象,可使用扩展运算符或Object.assign():
// jwt回调中修改返回逻辑 return token ? { ...token } : null;
注意:如果
expiry_date_time是Date类型,转成纯对象后会保留为Date实例,后续序列化JWT时会自动转为字符串,无需额外处理。
2. 按NextAuth规范构造返回对象
推荐将token的属性平铺或嵌套在标准结构中,既符合JWT要求,也便于后续session回调使用:
// jwt回调中返回结构化对象 return token ? { access_token: token.access_token, token_type: token.token_type, expires_in: token.expires_in, refresh_token: token.refresh_token, expiry_date_time: token.expiry_date_time.toISOString(), error: token.error } : null;
3. 调整authorize返回值逻辑
在authorize回调中直接返回普通对象,而非自定义Session类实例,从源头避免类实例问题:
// 修改authorize回调的返回值 return { user: userDetails, token: { ...authToken } // 提前转成普通对象 } as any;
为什么返回{ token: token }能解决问题?
因为此时返回的是一个普通对象,jose库可以正确识别并序列化它。AuthToken实例作为这个对象的属性值,在序列化过程中会被自动转为普通对象(前提是类的属性都是可枚举的),因此不会触发报错。
内容的提问来源于stack exchange,提问作者MoonKnight
相关产品推荐
相关产品推荐

