如何在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
相关产品推荐
相关产品推荐

