使用NextAuth与tRPC动态更新多租户NextJS应用的Session
多租户NextJS应用中动态更新NextAuth Session的解决方案
1. 配置NextAuth的JWT与Session回调
首先确保NextAuth使用JWT会话策略(这是动态更新session的前提),在[...nextauth].ts中配置回调,将orgId同步到JWT和Session:
import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { db } from "@/lib/db"; export const authOptions = { providers: [ CredentialsProvider({ async authorize(credentials) { // 登录逻辑:校验用户凭证,获取用户及其所属组织 const user = await db.user.findUnique({ where: { email: credentials?.email }, include: { memberships: true }, }); if (!user) return null; // 设置初始orgId(默认取第一个有权限的组织) const initialOrgId = user.memberships[0]?.orgId; return { id: user.id, email: user.email, orgId: initialOrgId }; }, }), ], session: { strategy: "jwt" }, // 必须启用JWT模式 callbacks: { async jwt({ token, user }) { // 登录时将orgId写入JWT if (user) token.orgId = user.orgId; return token; }, async session({ session, token }) { // 将JWT中的orgId同步到前端Session if (session.user) session.user.orgId = token.orgId as string; return session; }, }, }; export default NextAuth(authOptions);
2. 用tRPC实现切换组织的服务端接口
在tRPC路由中编写权限校验与Session更新逻辑,确保只有有权限的用户才能切换组织:
import { initTRPC, TRPCError } from "@trpc/server"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { db } from "@/lib/db"; import { z } from "zod"; const t = initTRPC.create(); export const appRouter = t.router({ switchOrganization: t.procedure .input(z.object({ orgId: z.string() })) .mutation(async ({ input }) => { const session = await getServerSession(authOptions); if (!session?.user?.id) throw new TRPCError({ code: "UNAUTHORIZED" }); // 校验用户是否拥有目标组织的访问权限 const hasAccess = await db.membership.findFirst({ where: { userId: session.user.id, orgId: input.orgId }, }); if (!hasAccess) throw new TRPCError({ code: "FORBIDDEN" }); // 更新JWT中的orgId(NextAuth提供的update方法) const auth = await import("next-auth"); await auth.update({ token: { orgId: input.orgId }, }); return { success: true, orgId: input.orgId }; }), }); export type AppRouter = typeof appRouter;
3. 客户端触发切换并刷新Session
在前端组件中调用tRPC接口,完成后刷新Session以获取最新的orgId:
"use client"; import { useSession } from "next-auth/react"; import { api } from "@/trpc/react"; export default function OrgSwitcher() { const { data: session, refetch } = useSession(); const switchOrgMutation = api.switchOrganization.useMutation({ onSuccess: async () => { // 刷新Session,同步最新的orgId到前端 await refetch(); }, }); const handleSwitchOrg = (targetOrgId: string) => { switchOrgMutation.mutate({ orgId: targetOrgId }); }; return ( <div className="flex gap-2"> <span>当前组织: {session?.user.orgId}</span> <button onClick={() => handleSwitchOrg("org_xxxx")}>切换到目标组织</button> </div> ); }
核心注意事项
- 必须使用JWT会话策略:数据库会话模式无法直接动态更新内容,JWT模式支持通过NextAuth的
update方法修改字段。 - 权限校验必须在服务端完成:禁止仅依赖客户端校验,防止恶意用户越权切换组织。
- Session刷新:调用
useSession的refetch方法会重新触发Session回调,拉取更新后的JWT数据,无需用户重新登录。
内容的提问来源于stack exchange,提问作者tdc
相关产品推荐
相关产品推荐

