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

Microsoft Graph交互式认证成功后报Error Access Denied错误

问题根因

这个ErrorAccessDenied并非身份认证流程故障——你已经能正常获取访问令牌、MSAL缓存中也存在有效账号,说明Azure AD登录环节是通的,报错触发点是初始化Graph客户端后调用GetUserInfo()发起实际Graph请求的阶段,常见诱因有三类:

  • 多租户应用权限同意范围覆盖不全。你将应用配置为「任意组织目录可用」的多租户模式,但仅在自己所属的租户完成了权限授予,其他员工所属租户未对该应用做管理员同意;若你配置的Graph权限不属于普通用户可自行同意的范围,其他用户拿到的访问令牌就不会携带对应权限声明,调用Graph接口自然被拒绝。
  • 权限类型配置错误。如果给应用配置的是应用程序权限(Application Permission)而非委托权限(Delegated Permission),用用户交互式登录拿到的委托令牌调用对应接口,或者配置完权限后未点击「授予管理员同意」完成租户级生效,都会触发访问拒绝。
  • 缓存账号读取逻辑有缺陷。你在InitializeGraphClientAsync中直接取缓存账号列表的第一个条目currentAccounts.FirstOrDefault()去兑换令牌,如果本地缓存残留了你之前测试登录的账号信息,其他员工登录时这里可能取到错误的账号对象,兑换出的令牌和当前用户上下文不匹配,就会抛出访问拒绝异常。
排查与修复方案
  1. 校验API权限配置
    • 进入Azure门户应用注册的「API权限」页,确认所有已配置的Microsoft Graph权限均为委托权限(你用的是用户交互式登录流,不需要应用程序类型权限)。
    • 检查权限列表的「同意状态」列,如果显示未对当前租户授予同意,点击顶部「授予管理员同意」按钮,完成本租户的权限全局生效。
    • 若确实需要支持其他Azure AD租户的用户访问,需告知对应租户的全局管理员在其租户内完成该应用的管理员同意,或者将权限调整为普通用户可自行同意的范围(比如基础权限User.Read默认允许用户自主同意,无需管理员操作)。同时确认PublicClientApplication初始化时使用common或organizations作为权威端点,不要硬编码你自己的租户ID,否则其他租户用户拿到的令牌受众不匹配也会报错。
  2. 修复账号读取的代码bug
    现有代码在SignIn方法中已经拿到了当前登录成功的账号对象,但调用初始化方法时没有传入,反而重新从缓存取第一个账号,很容易出现上下文错配。修改逻辑如下:
    • 调整InitializeGraphClientAsync方法签名,传入当前登录的准确账号对象,不再从缓存全量读取:
      private async Task InitializeGraphClientAsync(IAccount currentAccount)
      {
          try
          {
              if (currentAccount != null)
              {
                  GraphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
                      async (requestMessage) =>
                      {
                          var result = await PCA.AcquireTokenSilent(Scopes, currentAccount)
                              .ExecuteAsync();
      
                          requestMessage.Headers.Authorization =
                              new AuthenticationHeaderValue("Bearer", result.AccessToken);
                      }));
      
                  await GetUserInfo();
                  IsSignedIn = true;
              }
              else
              {
                  IsSignedIn = false;
              }
          }
          catch (Exception ex)
          {
              Debug.WriteLine("Failed to initialized graph client.");
              Debug.WriteLine($"Accounts in the msal cache: {(await PCA.GetAccountsAsync()).Count()}.");
              Debug.WriteLine($"See exception message for details: {ex.Message}");
              await SignOut();
          }
      }
      
    • 调整SignIn方法逻辑,在登录成功后保存当前账号,传给初始化方法,替换原来无参数的初始化调用:
      public async Task<string> SignIn()
      {
          var message = "";
          IAccount loggedInAccount = null;
          try
          {
              var accounts = await PCA.GetAccountsAsync();
              var silentAuthResult = await PCA.AcquireTokenSilent(Scopes, accounts.FirstOrDefault()).ExecuteAsync();
              Debug.WriteLine("User already signed in.");
              Debug.WriteLine($"Successful silent authentication for: {silentAuthResult.Account.Username}");
              Debug.WriteLine($"Access token: {silentAuthResult.AccessToken}");
              message = $"Successful silent authentication for: {silentAuthResult.Account.Username}";
              loggedInAccount = silentAuthResult.Account;
          }
          catch (MsalUiRequiredException msalEx)
          {
              Debug.WriteLine("Silent token request failed, user needs to sign-in: " + msalEx.Message);
              message = "Silent token request failed, user needs to sign-in: " + msalEx.Message;
              var interactiveRequest = PCA.AcquireTokenInteractive(Scopes);
              if (AuthUIParent != null)
              {
                  interactiveRequest = interactiveRequest
                      .WithParentActivityOrWindow(AuthUIParent);
              }
              var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
              Debug.WriteLine($"Successful interactive authentication for: {interactiveAuthResult.Account.Username}");
              Debug.WriteLine($"Access token: {interactiveAuthResult.AccessToken}");
              message = $"Successful interactive authentication for: {interactiveAuthResult.Account.Username}";
              loggedInAccount = interactiveAuthResult.Account;
          }
          catch (Exception ex)
          {
              Debug.WriteLine("Authentication failed. See exception messsage for more details: " + ex.Message);
              message = "Authentication failed. See exception messsage for more details: " + ex.Message;
          }
          await InitializeGraphClientAsync(loggedInAccount);
          return message;
      }
      
  3. 令牌有效性校验
    调试时解码其他员工登录返回的access token,检查令牌内的scp(委托权限)声明,确认包含GetUserInfo()调用所需的权限(比如User.Read),如果scp声明为空,直接回到权限配置环节重新检查同意状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:03:23