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

NextAuth+Cognito+HeyApi集成:NextJS令牌刷新后API授权失败

问题分析与解决步骤

核心问题

你现在的问题出在两个关键地方:

  1. 服务器端修改客户端变量无效:jwt回调是在服务器端运行的,你在那里设置OpenAPI.TOKEN,客户端的OpenAPI实例根本拿不到这个值,完全是做无用功。
  2. 令牌更新的竞态与滞后:useEffect依赖session更新令牌,但session的更新可能有延迟,加上有时候session?.access_token确实会出现undefined(比如令牌刷新过程中),导致API请求用了旧令牌或者空令牌。

具体修复方案

1. 移除服务器端的无效代码

删掉jwt回调里的这行代码:

OpenAPI.TOKEN = tokens.access_token as string;

服务器端的OpenAPI实例和客户端是完全隔离的,这里赋值对客户端请求没有任何作用。

2. 客户端用状态管理+请求拦截器动态注入令牌

不要直接给OpenAPI.TOKEN赋值,而是配置一个请求拦截器,每次请求时自动获取最新的令牌,同时用React状态维护当前有效令牌:

首先在你的仪表盘根组件里:

'use client';
import { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { OpenAPI } from '@/path-to-your-generated-api';

export default function DashboardLayout({ children }) {
  const { data: session } = useSession();
  const [accessToken, setAccessToken] = useState(session?.access_token);

  // 监听session变化,更新本地令牌状态
  useEffect(() => {
    if (session?.access_token) {
      setAccessToken(session.access_token);
    }
  }, [session]);

  // 配置OpenAPI请求拦截器,每次请求时注入最新令牌
  useEffect(() => {
    const interceptor = OpenAPI.interceptors.request.use((config) => {
      if (accessToken) {
        config.headers.Authorization = `Bearer ${accessToken}`;
      }
      return config;
    });

    // 组件卸载时移除拦截器
    return () => {
      OpenAPI.interceptors.request.eject(interceptor);
    };
  }, [accessToken]);

  return <>{children}</>;
}

注意:把@/path-to-your-generated-api换成你实际的API生成文件路径。

3. 优化NextAuth的session回调

确保session回调正确传递令牌,并且避免不必要的赋值:

async session({ session, token }) {
  // 直接扩展session,不要重新赋值整个对象
  session.access_token = token.access_token;
  session.refresh_token = token.refresh_token;
  session.expires_at = token.expires_at;
  session.error = token.error;
  
  // 这里也不需要设置OpenAPI.TOKEN,客户端会通过拦截器处理
  return session;
}

4. 处理竞态与重试逻辑(可选但推荐)

为了避免令牌刷新过程中API请求失败,可以给API客户端加一个重试机制:当收到401 Unauthorized时,触发一次令牌刷新,然后重试请求。

你可以在OpenAPI的响应拦截器里实现:

useEffect(() => {
  const responseInterceptor = OpenAPI.interceptors.response.use(
    (response) => response,
    async (error) => {
      const originalRequest = error.config;
      // 只处理401错误,并且只重试一次
      if (error.response?.status === 401 && !originalRequest._retry) {
        originalRequest._retry = true;
        // 调用NextAuth的refresh方法刷新令牌
        await signIn('cognito', { callbackUrl: '/dashboard', redirect: false });
        // 获取最新的session
        const { data: newSession } = await getSession();
        if (newSession?.access_token) {
          setAccessToken(newSession.access_token);
          // 更新原请求的Authorization头并重试
          originalRequest.headers.Authorization = `Bearer ${newSession.access_token}`;
          return OpenAPI.axiosInstance(originalRequest);
        }
      }
      return Promise.reject(error);
    }
  );

  return () => {
    OpenAPI.interceptors.response.eject(responseInterceptor);
  };
}, []);

注意:需要导入signIn和getSession方法:import { signIn, getSession } from 'next-auth/react';


为什么这样能解决问题

  • 请求拦截器确保每次API请求都用最新的令牌,避免了直接赋值OpenAPI.TOKEN带来的状态不一致问题。
  • 服务器端和客户端的令牌管理完全分离,不会出现服务器端赋值客户端拿不到的情况。
  • 重试逻辑处理了令牌刷新过程中的竞态条件,即使请求时令牌刚好过期,也能自动刷新并重试。

内容的提问来源于stack exchange,提问作者Charlie Østergaard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 09:51:23