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

在Next-Auth(React)中如何从Azure AD Token获取Microsoft Graph Token

解决Next-Auth AzureADProvider同时获取自有API和Microsoft Graph Token的问题

问题根源

一个Access Token的受众(aud)只能对应一个资源,你当前获取的Token是针对自有API(api://ownscope)的,无法直接用于访问Microsoft Graph(受众是https://graph.microsoft.com)。同时请求多个资源的Scope时,Azure AD默认返回第一个资源的Token,因此需要额外获取Graph专用的Token。

解决方案:使用On-Behalf-Of (OBO)流程获取Graph Token

OBO流程允许你的后端服务,用用户已有的自有API Token,向Azure AD请求针对Microsoft Graph的Access Token,这样就能同时拥有两个资源的访问权限。

步骤1:确认Azure AD应用配置

确保你的应用注册已添加Microsoft Graph的User.Read委托权限,并且完成了管理员同意(租户级应用需此操作)。

步骤2:实现OBO Token请求函数

在Next-Auth配置文件中添加以下函数,用于获取Graph专属Token:

async function getGraphAccessToken(ownApiToken: string, tenantId: string, clientId: string, clientSecret: string): Promise<string> {
  const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
  
  const formData = new URLSearchParams();
  formData.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
  formData.append('client_id', clientId);
  formData.append('client_secret', clientSecret);
  formData.append('assertion', ownApiToken);
  formData.append('scope', 'https://graph.microsoft.com/user.read');
  formData.append('requested_token_use', 'on_behalf_of');

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

  if (!response.ok) {
    throw new Error(`OBO Token请求失败: ${await response.text()}`);
  }

  const data = await response.json();
  return data.access_token;
}

步骤3:修改Next-Auth的JWT回调

在jwt回调中,先用自有API的Token通过OBO流程获取Graph Token,再用该Token调用Graph API:

export const authOptions: NextAuthOptions = {
  providers: [
    AzureADProvider({
      name: 'Azure Active Directory',
      clientId: process.env.AZURE_CLIENT_ID ?? 'undefined',
      clientSecret: process.env.AZURE_CLIENT_SECRET ?? 'undefined',
      tenantId: process.env.AZURE_TENANT_ID,
      authorization: {
        params: {
          scope: 'api://ownscope/general openid profile email', // 无需在这里加Graph的scope,OBO请求时会单独指定
        },
      },
    }),
  ],
  theme: {
    colorScheme: 'light',
  },
  callbacks: {
    async signIn() {
      return true;
    },
    async jwt({ token, account }) {
      if (account) {
        // 保存自有API的Token,用于后续访问自有服务
        token.own_access_token = account.access_token;
        
        try {
          // 通过OBO流程获取Graph Token
          const graphAccessToken = await getGraphAccessToken(
            account.access_token,
            process.env.AZURE_TENANT_ID!,
            process.env.AZURE_CLIENT_ID!,
            process.env.AZURE_CLIENT_SECRET!
          );
          
          // 使用Graph Token调用API获取用户信息
          const graphClient = getGraphClient({ access_token: graphAccessToken });
          token.graphdata = await graphClient.api('/me').get();
        } catch (err) {
          console.error('获取Graph数据失败');
          console.error(err);
        }
      }
      return token;
    },
    async session({ session, token }) {
      session.userdata = ToUser(token.graphdata);
      // 若前端需要自有API的Token,可在此添加到session中
      session.own_access_token = token.own_access_token;
      return session;
    },
  },
  events: {
    async signIn(message) {
      console.log('登录成功: %s', JSON.stringify(message.user.name));
    },
    async session(message) {
      console.log('会话活跃: %s', JSON.stringify(message?.session?.user?.name));
    },
  },
};

export default NextAuth(authOptions);

关键说明

  • OBO流程要求应用具备Delegated Permission,且用户已完成认证(拥有有效的自有API Token)。
  • 自有API的Token需保存到JWT中,方便后续访问自有服务;Graph Token仅用于获取用户数据,可按需保存。
  • 确保环境变量AZURE_TENANT_ID、AZURE_CLIENT_ID、AZURE_CLIENT_SECRET配置正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:09:29