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
相关产品推荐
相关产品推荐

