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

Next.js 14中使用Next-Auth实现Axios刷新令牌拦截器的问题

在Next.js 14中结合Next-Auth实现Axios刷新令牌拦截器

核心问题解决思路

useSession是React钩子,仅能在客户端组件的渲染周期内使用,无法直接在Axios拦截器这类非组件代码中调用。解决方案是使用Next-Auth提供的客户端异步函数getSession获取会话令牌,同时配合JWT会话策略实现令牌的更新与持久化。

步骤1:配置Next-Auth的JWT会话策略

确保你的[...nextauth].ts(App Router对应路径为app/api/auth/[...nextauth]/route.ts)配置为JWT策略,以便存储和更新访问/刷新令牌:

import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

export const authOptions = {
  session: {
    strategy: 'jwt', // 必须使用JWT策略才能自定义令牌存储
    maxAge: 3600, // 会话有效期,可根据令牌过期时间调整
  },
  providers: [
    CredentialsProvider({
      credentials: {
        email: { label: '邮箱', type: 'email' },
        password: { label: '密码', type: 'password' },
      },
      async authorize(credentials) {
        // 替换为你的登录接口调用逻辑
        const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/login`, {
          method: 'POST',
          body: JSON.stringify(credentials),
          headers: { 'Content-Type': 'application/json' },
        });
        const user = await res.json();

        if (res.ok && user) {
          // 返回包含令牌的用户对象
          return {
            id: user.id,
            email: user.email,
            accessToken: user.accessToken,
            refreshToken: user.refreshToken,
          };
        }
        return null;
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      // 登录时初始化令牌到JWT中
      if (user) {
        token.accessToken = user.accessToken;
        token.refreshToken = user.refreshToken;
      }
      return token;
    },
    async session({ session, token }) {
      // 将令牌注入会话,供客户端获取
      session.user.accessToken = token.accessToken as string;
      session.user.refreshToken = token.refreshToken as string;
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
};

export default NextAuth(authOptions);

步骤2:创建带拦截器的Axios实例

在lib/axios.ts中创建Axios实例,使用getSession获取令牌,并实现过期自动刷新逻辑:

import axios from 'axios';
import { getSession } from 'next-auth/react';

const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器:自动添加访问令牌
api.interceptors.request.use(async (config) => {
  const session = await getSession();
  if (session?.user.accessToken) {
    config.headers.Authorization = `Bearer ${session.user.accessToken}`;
  }
  return config;
});

// 响应拦截器:处理令牌过期并自动刷新
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // 仅处理401未授权错误,且避免重复重试
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      const session = await getSession();

      if (session?.user.refreshToken) {
        try {
          // 调用刷新令牌的API路由
          const refreshRes = await axios.post('/api/auth/refresh-token', {
            refreshToken: session.user.refreshToken,
          });

          const { accessToken: newAccessToken, refreshToken: newRefreshToken } = refreshRes.data;

          // 更新Next-Auth会话中的令牌
          await fetch('/api/auth/session', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              accessToken: newAccessToken,
              refreshToken: newRefreshToken,
            }),
          });

          // 更新原请求的令牌并重试
          originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
          return api(originalRequest);
        } catch (refreshError) {
          // 刷新失败,跳转至登录页
          window.location.href = '/auth/login';
          return Promise.reject(refreshError);
        }
      }
    }

    return Promise.reject(error);
  }
);

export default api;

步骤3:创建刷新令牌的API路由

在pages/api/auth/refresh-token.ts(App Router对应路径为app/api/auth/refresh-token/route.ts)中实现令牌刷新逻辑,确保后端验证刷新令牌并返回新令牌:

// Pages Router版本
import { NextApiRequest, NextApiResponse } from 'next';
import { getToken } from 'next-auth/jwt';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: '仅支持POST请求' });
  }

  const { refreshToken } = req.body;
  if (!refreshToken) {
    return res.status(400).json({ message: '缺少刷新令牌' });
  }

  // 调用你的后端刷新令牌接口
  const backendRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/refresh-token`, {
    method: 'POST',
    body: JSON.stringify({ refreshToken }),
    headers: { 'Content-Type': 'application/json' },
  });

  if (!backendRes.ok) {
    return res.status(401).json({ message: '刷新令牌无效' });
  }

  const newTokens = await backendRes.json();

  // 更新Next-Auth的JWT令牌
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  if (token) {
    token.accessToken = newTokens.accessToken;
    token.refreshToken = newTokens.refreshToken;
    // Next-Auth会自动处理JWT的更新,无需手动存储
  }

  res.status(200).json(newTokens);
}

Next.js 14兼容性说明

Next.js 14本身与Next-Auth v4.23+版本不存在核心兼容性问题。若出现异常,大概率是以下原因:

  • 未将会话策略设置为jwt:默认的database策略无法灵活更新令牌
  • Axios实例代码被导入到服务器组件中:getSession是客户端函数,只能在客户端环境运行,需在客户端组件(添加'use client'指令)中导入使用Axios实例
  • Next-Auth版本过旧:升级到最新的v4版本即可解决App Router相关适配问题

使用注意事项

  • 避免在服务器组件中直接使用该Axios实例,需在客户端组件(添加'use client')中导入
  • 刷新令牌的后端接口需做好权限验证,防止恶意使用
  • 可根据实际业务调整令牌过期的判断逻辑(比如解析JWT的exp字段提前刷新)

内容的提问来源于stack exchange,提问作者Jonathan Oliveira Bergamo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:37:48