在Server Action中手动更新Auth0会话遇Next.js报错
问题分析与解决方案
核心问题
- @auth0/nextjs-auth0 v4的
updateSession用法错误:v4版本中,更新会话必须结合Cookie上下文操作,直接传递session对象无法正确同步浏览器端Cookie,这是stale状态下刷新报错的核心原因。 - Server Action中错误使用
NextResponse.rewrite:标注'use server'的文件属于Server Action范畴,必须使用Next.js提供的redirect函数处理跳转,否则会触发Cookie更新的合规性提示。 - finally块逻辑错误:原代码中finally块强制跳转登录页,会覆盖令牌刷新成功的正常流程。
- 会话更新后未重新拉取有效数据:刷新令牌后未重新获取会话,可能导致后续请求使用旧令牌。
修正后的代码
'use server'; import { auth0 } from '../auth0-config'; import { tokenToExpire } from '../auth-utils'; import { redirect } from 'next/navigation'; import { SessionData } from '@auth0/nextjs-auth0/types'; import { cookies } from 'next/headers'; const BASE_URL = process.env.FISHDATA_API_BASE; const APP_BASE_URL = process.env.NEXT_PUBLIC_APP_BASE_URL as string; // 自定义认证错误类型 class AuthError extends Error { statusCode: number; constructor(message: string, statusCode: number) { super(message); this.statusCode = statusCode; } } export const fetchApi = async ( endpoint: string, options: FetchOptions = {} ): Promise<Response> => { const MAX_RETRIES = 3; const retryCount = options.retryCount || 0; if (retryCount >= MAX_RETRIES) { redirect('/login'); } const cookieStore = cookies(); let session = await auth0.getSession(cookieStore); if (!session) { redirect('/login'); } if (!session?.fishdata_access_token) { throw new AuthError('No access token found', 401); } // 检查令牌是否需要提前刷新 if (tokenToExpire(session.fishdata_access_token as string)) { await refreshToken(session, cookieStore); // 重新拉取更新后的会话 session = await auth0.getSession(cookieStore); if (!session?.fishdata_access_token) { throw new AuthError('Token refresh failed', 401); } } const requestHeaders: HeadersInit = { 'Content-Type': 'application/json', Authorization: `Bearer ${session.fishdata_access_token}`, ...(options.headers || {}) }; // 发起API请求 const response = await fetch(`${BASE_URL}${endpoint}`, { ...options, headers: requestHeaders }); // 处理401令牌失效响应 if (response.status === 401) { await refreshToken(session, cookieStore); return fetchApi(endpoint, { ...options, retryCount: retryCount + 1 }); } return response; }; const refreshToken = async (session: SessionData, cookieStore: ReturnType<typeof cookies>) => { try { const refreshResponse = await fetch(`${BASE_URL}/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: session.fishdata_refresh_token }) }); if (!refreshResponse.ok) { redirect('/login'); } const data = await refreshResponse.json(); if (!data.access_token) { throw new Error('No access token in refresh response'); } // 按照v4规范更新会话:传入cookie上下文确保Cookie同步 const updatedSession = { ...session, fishdata_access_token: data.access_token, fishdata_refresh_token: data.refresh_token }; await auth0.updateSession(cookieStore, updatedSession); return { success: true }; } catch (error) { console.error('Token refresh failed:', error); redirect('/login'); } };
关键修改说明
- 传递Cookie上下文:通过
cookies()获取的cookieStore对象,传递给getSession和updateSession,确保Auth0能正确读写浏览器Cookie,解决stale状态下的Cookie不同步问题。 - 替换跳转方式:用
next/navigation的redirect函数替代NextResponse.rewrite,符合Server Action的跳转规则。 - 移除错误的finally块:仅在刷新失败时跳转登录页,保留正常流程。
- 刷新后重新拉取会话:确保后续请求使用最新的令牌数据。
- 修正
updateSession调用:遵循v4版本的API规范,传入Cookie上下文和更新后的会话对象,保证Cookie正确更新。
内容的提问来源于stack exchange,提问作者godhar
相关产品推荐
相关产品推荐

