NextAuth中Refresh Token安全处理及AccessToken刷新方案咨询
解决NextAuth中Refresh Token安全存储与自动刷新问题
针对你的场景,核心思路是把Refresh Token仅存储在NextAuth服务端的JWT中,绝不暴露给客户端,同时利用NextAuth的jwt和session回调在服务端完成AccessToken的过期检查与刷新,客户端仅保留用于API调用的AccessToken。以下是具体实现步骤:
1. 配置Credentials Provider,登录时获取并存储Token
在登录回调(authorize)中,调用后端登录接口后,从响应的Cookie中解析Refresh Token和AccessToken,将它们存入NextAuth的JWT(仅服务端可见),而非直接返回给客户端。
import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import cookie from "cookie"; import jwt from "jsonwebtoken"; export default NextAuth({ providers: [ CredentialsProvider({ credentials: { email: { label: "邮箱", type: "email" }, password: { label: "密码", type: "password" } }, async authorize(credentials, req) { // 调用后端登录接口 const loginRes = await fetch(`${process.env.BACKEND_URL}/auth/login`, { method: "POST", body: JSON.stringify(credentials), headers: { "Content-Type": "application/json" }, credentials: "include" // 允许携带Cookie(跨域场景需后端配置CORS) }); const userData = await loginRes.json(); if (!loginRes.ok) return null; // 从响应头解析后端设置的HttpOnly Cookie const cookieStr = loginRes.headers.get("Set-Cookie"); const parsedCookies = cookie.parse(cookieStr || ""); // 将用户信息、AccessToken、Refresh Token一起返回,传给后续的jwt回调 return { ...userData, accessToken: parsedCookies.accessToken, refreshToken: parsedCookies.refreshToken }; } }) ], session: { strategy: "jwt" }, // 必须使用JWT策略,确保Token仅在服务端存储 callbacks: { // 2. JWT回调:处理Token过期检查与刷新 async jwt({ token, user }) { // 首次登录时,初始化JWT中的Token和过期时间 if (user) { token.accessToken = user.accessToken; token.refreshToken = user.refreshToken; token.user = user; // 解析AccessToken的过期时间(转成毫秒) const decodedToken = jwt.decode(user.accessToken); token.expiresAt = decodedToken.exp * 1000; return token; } // 检查AccessToken是否即将过期(提前60秒刷新,避免请求失败) const now = Date.now(); if (token.expiresAt && now < token.expiresAt - 60000) { return token; // 未过期,直接返回 } // AccessToken过期,调用后端刷新接口 try { const refreshRes = await fetch(`${process.env.BACKEND_URL}/auth/refresh`, { method: "POST", headers: { // 将存储在JWT中的Refresh Token传给后端验证 "Cookie": `refreshToken=${token.refreshToken}` }, credentials: "include" }); const newTokens = await refreshRes.json(); if (!refreshRes.ok) throw new Error("刷新Token失败"); // 更新JWT中的AccessToken和过期时间 token.accessToken = newTokens.accessToken; const newDecoded = jwt.decode(newTokens.accessToken); token.expiresAt = newDecoded.exp * 1000; // 如果后端返回新的Refresh Token,同步更新JWT中的值 if (newTokens.refreshToken) { token.refreshToken = newTokens.refreshToken; } return token; } catch (err) { console.error("Token刷新失败:", err); // 刷新失败,清空Token,触发客户端重新登录 return { ...token, accessToken: null, refreshToken: null, expiresAt: 0 }; } }, // 3. Session回调:仅返回客户端需要的信息,隐藏Refresh Token async session({ session, token }) { // 仅将用户信息和AccessToken传给客户端Session session.user = token.user; session.accessToken = token.accessToken; // 绝对不要将refreshToken、expiresAt等敏感字段返回给客户端 return session; } } });
关键细节说明
- JWT策略必要性:只有使用JWT策略,才能把Refresh Token存储在服务端的JWT中,客户端无法直接访问,彻底避免安全风险。
- 提前刷新Token:设置提前60秒刷新,避免客户端API请求因AccessToken刚好过期而失败。
- 客户端API调用:直接从
useSession获取accessToken,在请求头中携带(如Authorization: Bearer ${accessToken})即可。 - 服务端路由验证:在
getServerSideProps或middleware中调用getServerSession,通过返回的accessToken验证用户身份,无需依赖Refresh Token。 - 后端配合:后端的刷新接口需要接收Refresh Token(通过Cookie或请求头),验证后返回新的AccessToken(及可选的新Refresh Token),同时保持Cookie的HttpOnly、Secure属性。
内容的提问来源于stack exchange,提问作者Yeshenmaybe
相关产品推荐
相关产品推荐

