NextAuth v4中基于「记住我」设置JWT自定义过期的问题
解决NextAuth v4 Credentials Provider动态设置JWT过期时间问题
问题核心:NextAuth的JWT策略默认会强制使用session.maxAge或默认30天作为JWT及认证Cookie的过期时间,忽略手动设置的token.exp。要实现「记住我」动态过期,需同时处理JWT的exp字段和认证Cookie的过期时间,具体方案如下:
1. 持久化rememberMe状态并设置JWT过期时间
在jwt回调中,登录阶段将rememberMe存入token(用于后续刷新时保持状态),并计算对应过期时间;非登录阶段(token自动刷新)也需根据保存的rememberMe重新计算过期时间:
async jwt({ token, user }) { // 登录初始化阶段(user存在) if (user) { const thirtyDays = 30 * 24 * 60 * 60; const oneDay = 24 * 60 * 60; // 保存rememberMe到token,用于后续刷新判断 token.rememberMe = user.rememberMe; // 计算并设置JWT过期时间(秒级时间戳) token.exp = Math.floor(Date.now() / 1000) + (user.rememberMe ? thirtyDays : oneDay); // 挂载用户信息到token token.id = user.id; token.name = user.name; token.email = user.email; token.profilePicture = user.profilePicture; token.userType = user.userType; } // token自动刷新阶段,保持原过期策略 else if (token.rememberMe !== undefined) { const thirtyDays = 30 * 24 * 60 * 60; const oneDay = 24 * 60 * 60; token.exp = Math.floor(Date.now() / 1000) + (token.rememberMe ? thirtyDays : oneDay); } return token; }
2. 动态设置认证Cookie过期时间
通过cookie回调(NextAuth v4支持),根据token中的rememberMe值调整Cookie的maxAge(毫秒级),确保Cookie过期时间与JWT一致:
async cookie({ token, cookie }) { const thirtyDays = 30 * 24 * 60 * 60 * 1000; const oneDay = 24 * 60 * 60 * 1000; if (token.rememberMe !== undefined) { cookie.maxAge = token.rememberMe ? thirtyDays : oneDay; } return cookie; }
3. 移除固定session.maxAge
删除session选项中的固定maxAge配置,让系统自动跟随JWT的exp字段处理:
session: { strategy: 'jwt', // 不要设置固定maxAge },
4. 保持Session回调的过期时间同步
你原有的session回调已正确同步JWT过期时间到前端session对象,无需修改:
async session({ session, token }) { if (session.user && token) { session.expires = new Date(token.exp * 1000).toISOString(); session.user.id = token.id; session.user.name = token.name; session.user.email = token.email; session.user.profilePicture = token.profilePicture; session.user.userType = token.userType; } return session; }
完整修改后的代码
const authOptions : AuthOptions = { providers: [ CredentialsProvider({ name: 'Credentials', credentials: { email: {}, password: {}, rememberMe: {}, }, authorize: async (credentials) => { if (!credentials) throw new Error('No credentials provided.'); await dbConnect(); const user = await User.findOne({ email: credentials.email }).select('+password'); if (!user) return null; const isValid = await user.comparePassword(credentials.password, user.password); if (!isValid) return null; return { id: user._id, name: user.name, email: user.email, profilePicture: user.profilePicture, userType: user.userType, rememberMe: credentials.rememberMe === 'true', }; }, }), ], session: { strategy: 'jwt', }, secret: process.env.NEXTAUTH_SECRET, pages: { signIn: '/signin', }, callbacks: { async jwt({ token, user }) { if (user) { const thirtyDays = 30 * 24 * 60 * 60; const oneDay = 24 * 60 * 60; token.rememberMe = user.rememberMe; token.exp = Math.floor(Date.now() / 1000) + (user.rememberMe ? thirtyDays : oneDay); token.id = user.id; token.name = user.name; token.email = user.email; token.profilePicture = user.profilePicture; token.userType = user.userType; } else if (token.rememberMe !== undefined) { const thirtyDays = 30 * 24 * 60 * 60; const oneDay = 24 * 60 * 60; token.exp = Math.floor(Date.now() / 1000) + (token.rememberMe ? thirtyDays : oneDay); } return token; }, async session({ session, token }) { if (session.user && token) { session.expires = new Date(token.exp * 1000).toISOString(); session.user.id = token.id; session.user.name = token.name; session.user.email = token.email; session.user.profilePicture = token.profilePicture; session.user.userType = token.userType; } return session; }, async cookie({ token, cookie }) { const thirtyDays = 30 * 24 * 60 * 60 * 1000; const oneDay = 24 * 60 * 60 * 1000; if (token.rememberMe !== undefined) { cookie.maxAge = token.rememberMe ? thirtyDays : oneDay; } return cookie; } }, }
内容的提问来源于stack exchange,提问作者Zain Amjad




