NextAuth+Cognito+HeyApi集成:NextJS令牌刷新后API授权失败
问题分析与解决步骤
核心问题
你现在的问题出在两个关键地方:
- 服务器端修改客户端变量无效:
jwt回调是在服务器端运行的,你在那里设置OpenAPI.TOKEN,客户端的OpenAPI实例根本拿不到这个值,完全是做无用功。 - 令牌更新的竞态与滞后:
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
相关产品推荐
相关产品推荐

