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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:09:51