You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动