NextAuth中RefreshToken无法持久化到ServerSession的问题
NextAuth 仅在服务器会话存储 RefreshToken 的问题
我使用NextAuth时,已成功将accessToken存入服务器会话和客户端会话,但尝试把RefreshToken仅存入服务器会话时,发现只有在session回调中添加它才能在服务器会话里看到,可这样会同时暴露给客户端会话,这不是我想要的结果。
登录时jwt回调中打印的user对象:
user { accessToken: 'eyJhbGc...', refreshToken: 'eyJhbGc...', name: 'Company' }
服务器会话内容:
server session { user: { name: 'Company', email: undefined, image: undefined }, accessToken: 'eyJhbGc...', organisation: { name: 'Company' } }
我的route.js代码如下:
import NextAuth, { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { isJwtExpired } from "../../../../utils/Utils"; const backendBaseUrl = `${process.env.NEXT_PUBLIC_BACKEND_BASE_URL}`; const handleJWTExpired = async (token) => { console.log("Token expired, refreshing"); const backendBaseUrl = `${process.env.NEXT_PUBLIC_BACKEND_BASE_URL}`; const payload = JSON.stringify({ refresh: token.refreshToken, }); const response = await fetch(`${backendBaseUrl}/api/auth/token/refresh/`, { method: "POST", headers: { "Content-Type": "application/json", }, body: payload, }); if (response.ok) { console.log("Token refreshed"); // 从DRF后端提取返回的token const responseData = await response.json(); const { access: customAccessToken, access_expiration: customAccessExpiration, } = responseData; // 根据当前时间和过期时间计算'iat'和'exp' const now = Math.floor(Date.now() / 1000); // 当前Unix时间(秒) const expTime = new Date(customAccessExpiration).getTime() / 1000; // 将过期时间转换为Unix时间 token = { ...token, accessToken: customAccessToken, iat: now, exp: expTime, }; } else { token = { ...token, accessToken: "", }; console.log("响应错误", response.status); } return token; }; export const authOptions = { pages: { signIn: "/auth/signin", // 自定义登录页面路径 }, providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "邮箱", type: "text", placeholder: "user@username.com", }, password: { label: "密码", type: "password" }, }, async authorize(credentials, req) { // 携带邮箱和密码请求base_url/api/auth/login/ const url = `${backendBaseUrl}/api/token/`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(credentials), }); if (!response.ok) { return null; } const responseData = await response.json(); const { access: customAccessToken, refresh: customRefreshToken, organisation: { name }, } = responseData; if (customAccessToken) { const responseBody = { accessToken: customAccessToken, refreshToken: customRefreshToken, name: name, }; return responseBody; } else { return null; } }, }), ], secret: process.env.NEXTAUTH_SECRET, callbacks: { async jwt({ token, account, trigger, session, profile, user }) { // 用新数据更新服务器会话 if (account && user) { console.log('user', user) return { ...token, accessToken: user.accessToken, refreshToken: user.refreshToken, accessTokenExpires: Date.now() + user.expires_in * 1000, // 假设你有`expires_in`值 }; } // if (trigger === "update") { // console.log('更新服务器会话...') // return { ...token, ...session.organisation }; // } if ( typeof token.accessToken === "string" && isJwtExpired(token.accessToken) ) { token = await handleJWTExpired(token); } return token; }, async session({ session, token: jwtToken, user, trigger }) { const token = jwtToken; session.accessToken = token.accessToken; // session.refreshToken = token.refreshToken; // if (!session.user) { // session.organisation = { name: "" }; // } session.organisation = session.organisation || { name: "" }; session.organisation.name = token.name || null; // session.user.last_name = token.last_name || null; // session.user.location = token.location; // session.user.username = token.username; return session; }, }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
问题原因
NextAuth的session回调返回的内容会直接发送给客户端,所以在这里添加refreshToken必然会暴露。你看到服务器会话里没有refreshToken,是因为服务器端的JWT已经存储了refreshToken,但通过getServerSession获取的“服务器会话”只是session回调构造的对象,默认不包含未在回调中添加的字段——但这恰恰是安全的,因为refreshToken并没有丢失,只是没被放进返回给客户端的会话对象里。
正确解决方案
- 保持现有
session回调代码:不要取消注释session.refreshToken = token.refreshToken;,这样客户端永远拿不到refreshToken。 - 服务器端使用refreshToken直接从JWT获取:你已经在
handleJWTExpired函数中通过token.refreshToken获取了refreshToken,这个逻辑运行在服务器端,完全安全,客户端无法访问到JWT内部的内容。 - 验证服务器端是否持有refreshToken:可以在服务器API路由中用
getToken函数直接获取JWT内容,确认refreshToken存在:
import { getToken } from "next-auth/jwt"; export async function GET(request) { const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET }); console.log("服务器端JWT内容:", token); // 这里能看到refreshToken return new Response(JSON.stringify({ message: "查看控制台输出" })); }
总结
- refreshToken已经安全存储在服务器端的JWT中,不需要在
session回调中暴露给客户端。 - 服务器端需要使用refreshToken时,通过
getToken或在jwt回调中直接访问token.refreshToken即可,这些操作都在服务器端完成,不会泄露。 - 你打印的“服务器会话”是
session回调构造的对象,它本来就不包含refreshToken,这正是你想要的安全效果。
内容的提问来源于stack exchange,提问作者Jhnsbrst
相关产品推荐
相关产品推荐

