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

如何在Next.js App Router中更新Next-Auth的服务器端会话?

解决方案:Next.js 14 + NextAuth 服务器端更新会话并刷新JWT

1. 配置NextAuth支持Token刷新

首先在NextAuth路由配置中,强化jwt和session回调,让会话能同步最新的Token:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

export const authOptions = {
  providers: [
    CredentialsProvider({
      async authorize(credentials) {
        // 初始登录逻辑:调用后端获取access_token、refresh_token
        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 { ...user, id: user.userId };
        }
        return null;
      },
    }),
  ],
  session: { strategy: "jwt" }, // 必须使用JWT策略才能更新会话内容
  callbacks: {
    async jwt({ token, user, trigger, session }) {
      // 初始登录时,将API返回的tokens存入JWT
      if (user) {
        token.accessToken = user.access_token;
        token.refreshToken = user.refresh_token;
        token.expiresAt = user.expires_at;
      }

      // 手动触发会话更新时,同步新Token
      if (trigger === "update" && session?.accessToken) {
        token.accessToken = session.accessToken;
        token.expiresAt = session.expiresAt;
      }

      // 提前1分钟判断是否需要刷新(可选,也可在API拦截时手动处理)
      if (Date.now() < token.expiresAt - 60000) {
        return token;
      }

      return token;
    },
    async session({ session, token }) {
      // 将JWT中的Token同步到session对象,方便前后端获取
      session.user.accessToken = token.accessToken as string;
      session.user.refreshToken = token.refreshToken as string;
      session.user.expiresAt = token.expiresAt as number;
      return session;
    },
  },
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

2. 服务器端更新NextAuth会话

当通过刷新路由拿到新Token后,使用next-auth/server的update方法更新服务器端会话:

示例:完善你的刷新Token路由

// app/api/refresh-token/route.ts
import { getServerSession } from "next-auth/next";
import { update } from "next-auth/server";
import { authOptions } from "../auth/[...nextauth]/route";

export async function POST(request: Request) {
  const session = await getServerSession(authOptions);
  if (!session?.user.refreshToken) {
    return Response.json({ error: "无可用刷新令牌" }, { status: 401 });
  }

  // 调用后端刷新Token接口
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/refresh-token`, {
    method: "POST",
    body: JSON.stringify({ refresh_token: session.user.refreshToken }),
    headers: { "Content-Type": "application/json" },
  });

  const newTokens = await res.json();
  if (!res.ok) {
    return Response.json({ error: "刷新令牌失败" }, { status: 401 });
  }

  // 核心:更新服务器端NextAuth会话
  await update({
    session: {
      accessToken: newTokens.access_token,
      expiresAt: newTokens.expires_at,
    },
    authOptions,
  });

  return Response.json({ success: true, accessToken: newTokens.access_token });
}

服务器组件中处理401并更新会话

// app/dashboard/page.tsx
import { getServerSession } from "next-auth/next";
import { redirect } from "next/navigation";
import { authOptions } from "../api/auth/[...nextauth]/route";

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);
  let data;

  try {
    data = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/protected-data`, {
      headers: { Authorization: `Bearer ${session?.user.accessToken}` },
    }).then(res => {
      if (res.status === 401) throw new Error("未授权");
      return res.json();
    });
  } catch (err) {
    // 调用刷新Token接口
    const refreshRes = await fetch("/api/refresh-token", { method: "POST" });
    if (!refreshRes.ok) {
      redirect("/auth/login");
    }

    // 获取更新后的会话,重新发起请求
    const updatedSession = await getServerSession(authOptions);
    data = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/protected-data`, {
      headers: { Authorization: `Bearer ${updatedSession?.user.accessToken}` },
    }).then(res => res.json());
  }

  return <div>{JSON.stringify(data)}</div>;
}

3. 客户端API客户端错误拦截

如果是客户端API调用触发401,可在自定义API客户端中拦截并更新客户端会话:

// lib/api-client.ts
import { getSession, updateSession, signOut } from "next-auth/react";

const apiClient = async (url: string, options: RequestInit = {}) => {
  const session = await getSession();
  const headers = {
    ...options.headers,
    Authorization: `Bearer ${session?.user.accessToken}`,
  };

  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
    ...options,
    headers,
  });

  if (res.status === 401) {
    // 调用刷新Token路由
    const refreshRes = await fetch("/api/refresh-token", { method: "POST" });
    if (!refreshRes.ok) {
      signOut();
      return Promise.reject(new Error("刷新令牌失败"));
    }

    const newTokens = await refreshRes.json();
    // 更新客户端会话
    await updateSession({
      user: { ...session?.user, accessToken: newTokens.accessToken },
    });

    // 重新发起原请求
    return fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
      ...options,
      headers: { ...headers, Authorization: `Bearer ${newTokens.accessToken}` },
    });
  }

  return res;
};

export default apiClient;

关键注意事项

  • 必须将NextAuth的session.strategy设为"jwt",数据库会话策略无法直接更新会话内容。
  • 刷新Token后需同步更新JWT中的accessToken和expiresAt,否则会话会继续使用旧Token。
  • 服务器端更新会话用next-auth/server的update方法(需传入authOptions),客户端用next-auth/react的updateSession。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:47:12