You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next Auth会话中Token不定期消失问题的解决请求

解决NextAuth Google认证令牌不定期消失的问题

你的代码里存在几个关键逻辑错误,导致令牌刷新失败后数据丢失,以下是修复方案:

核心问题分析

  1. 错误使用Access Token作为Refresh Token:Google OAuth的refresh_token是独立返回的字段,你用token.access_token去请求刷新,完全不符合接口要求,导致刷新失败后令牌失效。
  2. JWT回调未保留原有数据:后续刷新页面时,account和user参数为undefined,此时如果刷新逻辑出错,会导致原有令牌数据丢失。
  3. 令牌刷新逻辑不完善:没有判断令牌是否需要刷新,每次都调用刷新接口,且未处理刷新失败后的会话清理。

修复后的完整代码

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 };

关键修改说明

  1. 正确保存和使用Refresh Token:

    • 首次登录时,将account.refresh_token保存到JWT中
    • 刷新接口使用真实的refresh_token请求,不再误用access_token
  2. 优化JWT回调逻辑:

    • 仅在首次登录时初始化令牌数据,后续请求保留原有token
    • 增加令牌过期时间判断,只有在即将过期时才触发刷新,减少不必要的请求
  3. 完善错误处理:

    • 刷新失败时标记错误,会话回调中自动登出,避免无效会话残留
    • 处理Google返回的错误响应,确保错误信息可追踪
  4. 简化配置:

    • 移除session配置中冗余的jwt: true
    • 明确设置JWT和会话的有效期,避免默认值导致的问题

内容的提问来源于stack exchange,提问作者Tárek Holanda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 08:39:53