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

如何通过NextAuth与Boxyhq SAML-Jackson实现Azure AD SAML令牌转OAuth令牌

实现SAML令牌兑换为Azure AD OAuth令牌的方案

核心思路

利用Azure AD的On-Behalf-Of (OBO) 授权流程,将Boxyhq返回的SAML断言(即NextAuth中的access_token)兑换为可用于第三方API的OAuth令牌,全程无需用户额外操作。


步骤1:配置Azure AD权限与应用

  1. 在Azure AD中,确保你的SAML企业应用已启用OAuth2.0支持:
    • 进入该应用的**“API权限”**页面,添加目标第三方API的委托权限(例如Microsoft Graph的User.Read),并完成管理员同意。
    • 注册一个单独的OAuth客户端应用(或复用现有SAML应用),记录其client_id和client_secret,确保该应用被允许使用OBO流程。
  2. 确认Azure AD信任SAML令牌的签发者:在Azure AD的**“企业应用”**中,你的SAML应用的Issuer值需与Boxyhq配置的issuer一致。

步骤2:从NextAuth/Boxyhq获取SAML断言

在NextAuth的认证流程中,Boxyhq会返回包含用户SAML断言的access_token(格式为JWT)。你可以在NextAuth的回调函数中获取该令牌,用于后续兑换。


步骤3:实现OBO令牌兑换逻辑

在NextAuth的服务器端回调中(如jwt或signIn回调),调用Azure AD的令牌端点完成兑换:

示例代码([...nextauth].js)

import NextAuth from "next-auth";
import BoxyhqSAML from "@boxyhq/saml-jackson";

export default NextAuth({
  providers: [
    BoxyhqSAML({
      issuer: process.env.BOXYHQ_ISSUER,
      clientId: process.env.BOXYHQ_CLIENT_ID,
      clientSecret: process.env.BOXYHQ_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    async jwt({ token, account }) {
      // 仅在用户首次登录或令牌更新时执行兑换
      if (account?.access_token && !token.oauthAccessToken) {
        const tokenEndpoint = `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/oauth2/v2.0/token`;
        
        const formData = new URLSearchParams({
          grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
          client_id: process.env.AZURE_OAUTH_CLIENT_ID,
          client_secret: process.env.AZURE_OAUTH_CLIENT_SECRET,
          assertion: account.access_token,
          scope: "https://graph.microsoft.com/User.Read", // 替换为目标API的权限范围
          requested_token_use: "on_behalf_of",
        });

        const response = await fetch(tokenEndpoint, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: formData,
        });

        const tokenData = await response.json();
        
        // 将兑换得到的OAuth令牌存入JWT
        token.oauthAccessToken = tokenData.access_token;
        token.oauthRefreshToken = tokenData.refresh_token;
        token.oauthExpiresAt = Date.now() + tokenData.expires_in * 1000;
      }

      // 自动刷新过期的OAuth令牌
      if (token.oauthRefreshToken && Date.now() > token.oauthExpiresAt - 60000) {
        const tokenEndpoint = `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/oauth2/v2.0/token`;
        
        const formData = new URLSearchParams({
          grant_type: "refresh_token",
          client_id: process.env.AZURE_OAUTH_CLIENT_ID,
          client_secret: process.env.AZURE_OAUTH_CLIENT_SECRET,
          refresh_token: token.oauthRefreshToken,
          scope: "https://graph.microsoft.com/User.Read",
        });

        const response = await fetch(tokenEndpoint, {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: formData,
        });

        const tokenData = await response.json();
        
        token.oauthAccessToken = tokenData.access_token;
        token.oauthRefreshToken = tokenData.refresh_token ?? token.oauthRefreshToken;
        token.oauthExpiresAt = Date.now() + tokenData.expires_in * 1000;
      }

      return token;
    },
    async session({ session, token }) {
      // 将OAuth令牌注入session,供前端或API路由使用
      session.user.oauthAccessToken = token.oauthAccessToken;
      return session;
    },
  },
  session: { strategy: "jwt" }, // 必须使用JWT策略存储令牌
});

关键注意事项

  • 安全要求:client_secret必须存储在环境变量中,绝对不能暴露给前端,兑换逻辑必须在服务器端执行(NextAuth的回调默认在服务器端运行)。
  • 令牌有效性:确保Boxyhq返回的access_token是有效的JWT,包含sub(用户唯一标识)、iss(签发者)等Azure AD认可的声明。
  • 权限范围:scope参数必须与目标第三方API要求的权限完全匹配,且已获得管理员同意。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 05:43:14