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

NextJS 14 令牌管理中间件页面刷新异常问题求助

问题:NextJS 14中access_token过期后刷新令牌生效但页面仍报错

我使用NextJS 14,通过中间件处理refresh_token与access_token的Cookie逻辑,同时采用NextAuth做身份认证,这个问题已经卡了一周,求解决。

middleware.ts代码

export default withAuth(
  async function middleware(req) {
    const url = req.nextUrl.clone();

    // Redirect root path to /courses
    if (req.nextUrl.pathname === "/") {
      url.pathname = "/courses";
      return NextResponse.redirect(url);
    }

    const response = NextResponse.next();
    const refreshToken = req.cookies.get("refresh_token")?.value;
    const accessToken = req.cookies.get("access_token")?.value;

    console.log("refreshTokenNew", refreshToken);

    // Handling refresh token
    if (!accessToken && refreshToken) {
      try {
        const serverResponse = await fetch(`${Backend_URL}/user/refresh-token`, {
          method: "POST",
          headers: {
            Cookie: `refresh_token=${refreshToken}`
          },
          credentials: "include",
        });

        if (serverResponse.ok) {
          const setCookieHeader = serverResponse.headers.get("set-cookie");
          setCookiesFromHeader(response, setCookieHeader);
          console.log("set cookie header", setCookieHeader);

          return response; // return NextResponse.redirect(url)
        } else {
          req.cookies.clear();
          return NextResponse.redirect(`${Frontend_URL}/login`);
        }
      } catch (error) {
        console.error("Middleware error:", error);
        return NextResponse.redirect(`${Frontend_URL}/login`);
      }
    }

    return response;
  },
  {
    callbacks: {
      authorized: ({ req, token }) => {
        // Redirect unauthenticated users trying to access dashboard routes
        if (req.nextUrl.pathname.startsWith("/dashboard") && !token) {
          return false;
        }

        // Prevent students from accessing teacher paths
        if (req.nextUrl.pathname.startsWith("/teacher") &&
            token &&
            token.role === "STUDENT") {
          return false;
        }

        return true;
      },
    },
  }
);

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

服务端组件的fetch请求代码

export async function makeFetch(url: string, options: RequestInit = {}) {
  // Set default options, especially for credentials
  const defaultOptions: RequestInit = {
    method: "GET",
    credentials: "include", // This should be a value from 'omit', 'same-origin', or 'include'
    headers: { Cookie: cookies().toString() },
    ...options,
  };

  let response = await fetch(url, defaultOptions);

  // Retry the request if the response is 401
  if (response.status === 401) {
    response = await fetch(url, defaultOptions);
  }

  return response;
}

const EditCourse = async ({ params }: Props) => {
  const response = await makeFetch(`${Backend_URL}/courses/${params.courseId}/teacher`);

  // Check if the request was successful
  if (response.ok) {
    const course = await response.json();
    return (
      <div>
        Edit course {params.courseId}
        {JSON.stringify(course.data)}
      </div>  
    );
  } else {
    // Handle other errors
    const errorText = await response.text();
    return <div>Error: {errorText}</div>;
  }
};

export default EditCourse;

当前问题

当access_token过期后,能看到新的access_token Cookie已设置,但页面仍显示错误,必须刷新页面才能正常加载课程内容。尝试在中间件中添加NextResponse.redirect(new URL(req.url))重定向到当前页面,却陷入请求过多循环,提示需删除Cookie。逻辑上设置新access_token后,if (!accessToken && refreshToken)代码块不应再执行,求解决方法。


解决方案

1. 中间件添加带标记的重定向,避免循环

问题核心是:服务端组件的请求在中间件设置新Cookie前就已经发起,所以当前请求用的还是旧Cookie。需要重定向让页面重新发起请求,同时用临时参数标记避免循环。

修改middleware.ts:

  • 在开头添加处理标记的逻辑:
const url = req.nextUrl.clone();

// 处理令牌刷新后的重定向标记,移除参数并返回
if (url.searchParams.has('token_refreshed')) {
  url.searchParams.delete('token_refreshed');
  const redirectResponse = NextResponse.redirect(url);
  // 保留现有Cookie
  req.cookies.forEach((cookie) => {
    redirectResponse.cookies.set(cookie.name, cookie.value);
  });
  return redirectResponse;
}
  • 在刷新令牌成功的分支,替换原返回逻辑:
if (serverResponse.ok) {
  const setCookieHeader = serverResponse.headers.get("set-cookie");
  setCookiesFromHeader(response, setCookieHeader);
  
  // 添加重定向标记,触发页面重新请求
  url.searchParams.set('token_refreshed', 'true');
  const redirectResponse = NextResponse.redirect(url);
  // 将新Cookie加入重定向响应
  if (setCookieHeader) {
    redirectResponse.headers.append('Set-Cookie', setCookieHeader);
  }
  return redirectResponse;
}

2. 优化服务端组件的fetch重试逻辑

当前重试时用的还是旧Cookie,需要重新获取最新Cookie再重试:

import { cookies } from 'next/headers';

export async function makeFetch(url: string, options: RequestInit = {}) {
  const defaultOptions: RequestInit = {
    method: "GET",
    credentials: "include",
    headers: { Cookie: cookies().toString() },
    ...options,
  };

  let response = await fetch(url, defaultOptions);

  if (response.status === 401) {
    // 重新获取最新Cookie再重试
    const updatedCookies = cookies().toString();
    const retryOptions = {
      ...defaultOptions,
      headers: { ...defaultOptions.headers, Cookie: updatedCookies },
    };
    response = await fetch(url, retryOptions);
  }

  return response;
}

3. 同步NextAuth会话令牌

因为使用了NextAuth,要确保中间件中authorized回调的token与新的access_token同步。可以在刷新令牌成功后,调用NextAuth的update方法更新会话,或者在authorized回调中重新验证用户身份(比如用新的access_token请求用户信息)。


内容的提问来源于stack exchange,提问作者Noussa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:02:05