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

在Server Action中手动更新Auth0会话遇Next.js报错

问题分析与解决方案

核心问题

  1. @auth0/nextjs-auth0 v4的updateSession用法错误:v4版本中,更新会话必须结合Cookie上下文操作,直接传递session对象无法正确同步浏览器端Cookie,这是stale状态下刷新报错的核心原因。
  2. Server Action中错误使用NextResponse.rewrite:标注'use server'的文件属于Server Action范畴,必须使用Next.js提供的redirect函数处理跳转,否则会触发Cookie更新的合规性提示。
  3. finally块逻辑错误:原代码中finally块强制跳转登录页,会覆盖令牌刷新成功的正常流程。
  4. 会话更新后未重新拉取有效数据:刷新令牌后未重新获取会话,可能导致后续请求使用旧令牌。

修正后的代码

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

关键修改说明

  1. 传递Cookie上下文:通过cookies()获取的cookieStore对象,传递给getSession和updateSession,确保Auth0能正确读写浏览器Cookie,解决stale状态下的Cookie不同步问题。
  2. 替换跳转方式:用next/navigation的redirect函数替代NextResponse.rewrite,符合Server Action的跳转规则。
  3. 移除错误的finally块:仅在刷新失败时跳转登录页,保留正常流程。
  4. 刷新后重新拉取会话:确保后续请求使用最新的令牌数据。
  5. 修正updateSession调用:遵循v4版本的API规范,传入Cookie上下文和更新后的会话对象,保证Cookie正确更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:40:16