如何在Auth.js v5中实现服务端登出?Next.js14对接.NET问题
Next.js 14 + Auth.js v5 对接.NET服务时服务端未同步登出的解决方案
问题背景
在Next.js 14应用中通过Auth.js v5实现自定义OAuth方案对接.NET服务,点击客户端登出按钮后本地会话清除成功,但.NET服务端未同步执行登出操作,需要实现两端登出的同步。
现有代码
客户端登出组件(React)
import { signIn, signOut } from "@/lib/auth" import { Button } from "./ui/button" export function SignOut(props: React.ComponentPropsWithRef<typeof Button>) { return ( <form onSubmit={async (event) => { event.preventDefault(); await signOut(); }} className="w-full" > <Button variant="ghost" className="w-full p-0" {...props}> Sign Out </Button> </form> ) }
.NET服务端登出方法(C#)
[ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken] public async Task<IActionResult> LogoutPost() { await _signInManager.SignOutAsync(); return SignOut( authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties: new AuthenticationProperties { RedirectUri = "/" }); }
Next.js Auth.js配置(TypeScript)
import NextAuth from "next-auth" import "next-auth/jwt" import { Provider } from 'next-auth/providers' import type { NextAuthConfig } from "next-auth" export default function OwnIdentityProvider(options: any): Provider { return { id: "chirp-spa", name: "Chirp SPA Client", type: "oidc", ... profile: (profile: any) => { return { id: profile.sub, email: profile.email, name: profile.name } }, clientId: process.env.AUTH_IDENTITY_ID as string, clientSecret: process.env.AUTH_IDENTITY_SECRET as string, } } const config: NextAuthConfig = { theme: { logo: "https://authjs.dev/img/logo-sm.png" }, providers: [ OwnIdentityProvider({}) ], secret: process.env.SECRET, events: { async signOut() { console.log("User signed out from client"); }, }, experimental: { enableWebAuthn: true, } }; export const { handlers, auth, signIn, signOut } = NextAuth(config) declare module "next-auth" { interface Session { accessToken?: string } } declare module "next-auth/jwt" { interface JWT { accessToken?: string } }
解决方案
1. 完善OIDC提供者配置
在自定义OIDC提供者中添加服务端登出端点,让Auth.js自动触发服务端登出流程:
export default function OwnIdentityProvider(options: any): Provider { return { id: "chirp-spa", name: "Chirp SPA Client", type: "oidc", issuer: process.env.NET_AUTH_SERVER_URL, // 配置.NET认证服务地址 endSessionEndpoint: `${process.env.NET_AUTH_SERVER_URL}/connect/logout`, // 指定服务端登出接口 ... profile: (profile: any) => { return { id: profile.sub, email: profile.email, name: profile.name } }, clientId: process.env.AUTH_IDENTITY_ID as string, clientSecret: process.env.AUTH_IDENTITY_SECRET as string, } }
2. 修改客户端登出调用
更新signOut方法参数,强制触发服务端登出并重定向:
await signOut({ callbackUrl: "/", redirect: true, // 触发Auth.js的OIDC登出流程 });
3. 解决跨域与AntiForgeryToken问题
由于.NET的登出接口启用了ValidateAntiForgeryToken,SPA直接调用会存在跨域和令牌验证问题,需通过Next.js API路由转发请求:
创建API转发路由(app/api/auth/signout-proxy/route.ts)
import { auth } from "@/lib/auth"; export async function POST() { const session = await auth(); if (!session?.accessToken) { return new Response('Unauthorized', { status: 401 }); } // 先获取.NET的AntiForgeryToken(可选,根据.NET配置调整) const antiForgeryRes = await fetch(`${process.env.NET_AUTH_SERVER_URL}/connect/antiforgery`, { credentials: 'include' }); const antiForgeryToken = antiForgeryRes.headers.get('RequestVerificationToken'); // 调用.NET登出接口 await fetch(`${process.env.NET_AUTH_SERVER_URL}/connect/logout`, { method: 'POST', headers: { 'Authorization': `Bearer ${session.accessToken}`, 'RequestVerificationToken': antiForgeryToken || '' }, credentials: 'include' }); return new Response(null, { status: 200 }); }
调整.NET登出接口(可选)
如果SPA场景下AntiForgeryToken验证难以适配,可临时调整接口验证逻辑(不推荐直接关闭,建议改用Bearer Token辅助验证):
[ActionName(nameof(Logout)), HttpPost("~/connect/logout")] public async Task<IActionResult> LogoutPost() { await _signInManager.SignOutAsync(); return SignOut( authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, properties: new AuthenticationProperties { RedirectUri = "/" }); }
配置.NET跨域CORS
允许Next.js应用的跨域请求:
builder.Services.AddCors(options => { options.AddPolicy("AllowNextApp", policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); app.UseCors("AllowNextApp");
4. 在Auth.js事件中触发服务端登出
如果自动OIDC登出流程未生效,可在signOut事件中手动调用API转发路由:
const config: NextAuthConfig = { ... events: { async signOut({ token }) { if (token.accessToken) { try { await fetch('/api/auth/signout-proxy', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('服务端登出失败:', error); } } }, }, ... };
内容的提问来源于stack exchange,提问作者Rasik
相关产品推荐
相关产品推荐

