Next Auth会话中Token不定期消失问题的解决请求
解决NextAuth Google认证令牌不定期消失的问题
你的代码里存在几个关键逻辑错误,导致令牌刷新失败后数据丢失,以下是修复方案:
核心问题分析
- 错误使用Access Token作为Refresh Token:Google OAuth的refresh_token是独立返回的字段,你用
token.access_token去请求刷新,完全不符合接口要求,导致刷新失败后令牌失效。 - JWT回调未保留原有数据:后续刷新页面时,
account和user参数为undefined,此时如果刷新逻辑出错,会导致原有令牌数据丢失。 - 令牌刷新逻辑不完善:没有判断令牌是否需要刷新,每次都调用刷新接口,且未处理刷新失败后的会话清理。
修复后的完整代码
import NextAuth from "next-auth"; import { signOut } from "next-auth/react"; import GoogleProvider from "next-auth/providers/google"; const GOOGLE_AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth?" + new URLSearchParams({ prompt: "consent", access_type: "offline", response_type: "code" }); async function refreshAccessToken(token) { try { // 只有当refresh_token存在时才尝试刷新 if (!token.refresh_token) throw new Error("No refresh token available"); const urlRefresh = "https://oauth2.googleapis.com/token"; const formData = new URLSearchParams({ client_id: process.env.GOOGLE_CLIENT_ID, client_secret: process.env.GOOGLE_CLIENT_SECRET, grant_type: "refresh_token", refresh_token: token.refresh_token }); const refreshedResponse = await fetch(urlRefresh, { method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"}, body: formData }); const refreshedToken = await refreshedResponse.json(); if (!refreshedResponse.ok) throw refreshedToken; return { ...token, id_token: refreshedToken.id_token || token.id_token, access_token: refreshedToken.access_token, // 如果Google返回新的refresh_token,更新它(部分场景会返回) refresh_token: refreshedToken.refresh_token || token.refresh_token, expires_at: Date.now() + refreshedToken.expires_in * 1000 }; } catch (error) { console.error("Token refresh failed:", error); // 刷新失败,返回带错误标记的token,后续会话回调可以处理登出 return { ...token, error: "RefreshAccessTokenError" }; } } export const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, authorizationUrl: GOOGLE_AUTHORIZATION_URL, domain: process.env.GOOGLE_DOMAIN, // 指定需要获取的权限,确保能拿到refresh_token authorization: { params: { scope: "openid email profile" } } }), ], secret: process.env.JWT_SECRET, session: { strategy: "jwt", // 设置会话有效期,避免长期无效会话 maxAge: 24 * 60 * 60 // 1天 }, jwt: { secret: process.env.JWT_SECRET, // JWT有效期,建议比Google令牌有效期长 maxAge: 30 * 24 * 60 * 60 // 30天 }, pages: { signIn: "/login", }, callbacks: { async signIn({ profile }) { return profile.email.endsWith("@companyName.com"); }, async redirect({ url, baseUrl }) { return baseUrl; }, async jwt({ token, user, account }) { // 首次登录:保存account和user数据到token if (account && user) { return { ...token, id_token: account.id_token, access_token: account.access_token, refresh_token: account.refresh_token, user, expires_at: Date.now() + account.expires_in * 1000 }; } // 后续请求:检查令牌是否即将过期,再决定是否刷新 if (Date.now() < token.expires_at - 60 * 1000) { return token; } // 令牌即将过期,尝试刷新 return refreshAccessToken(token); }, async session({ session, token }) { // 如果刷新失败,触发登出 if (token.error === "RefreshAccessTokenError") { await signOut({ redirect: false }); return {}; } // 将令牌数据附加到会话 session.user = token.user; session.id_token = token.id_token; session.access_token = token.access_token; return session; }, }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
关键修改说明
正确保存和使用Refresh Token:
- 首次登录时,将
account.refresh_token保存到JWT中 - 刷新接口使用真实的refresh_token请求,不再误用access_token
- 首次登录时,将
优化JWT回调逻辑:
- 仅在首次登录时初始化令牌数据,后续请求保留原有token
- 增加令牌过期时间判断,只有在即将过期时才触发刷新,减少不必要的请求
完善错误处理:
- 刷新失败时标记错误,会话回调中自动登出,避免无效会话残留
- 处理Google返回的错误响应,确保错误信息可追踪
简化配置:
- 移除session配置中冗余的
jwt: true - 明确设置JWT和会话的有效期,避免默认值导致的问题
- 移除session配置中冗余的
内容的提问来源于stack exchange,提问作者Tárek Holanda
相关产品推荐
相关产品推荐

