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

ASP.NET Framework直接获取用户委托的Graph访问令牌问题

ASP.NET Framework 直接访问 MS Graph 解决 AADSTS65001 及 Claims 挑战问题

问题概述

需要实现ASP.NET Framework应用直接访问Microsoft Graph,无需桌面应用配合,通过应用自身触发Microsoft认证并请求用户授权权限范围。当前获取访问令牌时持续返回AADSTS65001错误,提示用户/管理员未同意应用权限,且包含Claims挑战信息,不清楚如何让用户确认该Claims。

错误详情

AADSTS65001: 用户或管理员未同意使用 ID 为 '00aa00aa-bb11-cc22-dd33-44ee44ee44ee'、名为 'Auth-1' 的应用。请为此用户和资源发送交互式授权请求。
跟踪 ID: 00aa00aa-bb11-cc22-dd33-44ee44ee44ee
关联 ID: 00aa00aa-bb11-cc22-dd33-44ee44ee44ee
时间戳: 2025-02-03 09:58:48Z 返回的错误包含Claims挑战。有关如何处理与多因素身份验证、条件访问和增量同意相关的Claims的详细信息,请参考相关文档。如果使用代表流,请参考对应文档。

Claims格式:

{
    "access_token": 
    { 
        "capolids":
        { 
            "essential": true,
            "values": [ "00aa00aa-bb11-cc22-dd33-44ee44ee44ee"]
        }
    }
}

解决思路与步骤

1. 捕获并处理MsalUiRequiredException中的Claims挑战

当调用AcquireTokenByAuthorizationCode抛出MsalUiRequiredException时,异常中会包含Claims信息。需要提取这些Claims,然后触发重新登录,在授权请求中带上Claims参数,引导用户完成权限同意和条件验证。

2. 调整OpenID Connect通知配置

在OpenIdConnectAuthenticationNotifications中添加RedirectToIdentityProvider通知,用于在需要时注入Claims参数到授权请求中。同时修改AuthorizationCodeReceived中的异常处理逻辑,将捕获的Claims存储到会话中,供后续授权请求使用。

3. 确认API权限配置

从截图看,应用已添加User.Read的委派权限:
API权限截图

  • 如果权限需要管理员同意(如某些敏感权限),需先由租户管理员完成同意操作;
  • 如果是用户可同意的权限,确保首次登录时引导用户完成授权同意。

4. 修正代码中的配置问题

  • 确保RedirectUri在Azure AD应用注册中已配置,且与代码中的一致;
  • Scope需包含所需的Graph权限(如user.read),同时保留openid email profile offline_access等OpenID Connect基础权限。

修改后的代码示例

namespace Example
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var authInfo = new AuthInfo
            {
                AuthenticationType = "Auth-1",
                ClientId = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
                AadInstance = "https://login.microsoftonline.com/",
                Domain = "company.onmicrosoft.com",
                TenantId = "00aa00aa-bb11-cc22-dd33-44ee44ee44ee",
                RedirectUri = "https://localhost:44323/About",
                PostLogoutRedirectUri = "https://localhost:44323/About",
                AppSecret = "your-client-secret-here"
            };

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
                AuthenticationMode = AuthenticationMode.Passive,
                CookiePath = HostingEnvironment.ApplicationVirtualPath,
                CookieHttpOnly = true,
                ExpireTimeSpan = TimeSpan.FromMinutes(5),
            });

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                AuthenticationType = authInfo.AuthenticationType,
                RedirectUri = authInfo.RedirectUri, // 修正:使用登录回调地址而非登出地址
                ClientId = authInfo.ClientId,
                Authority = $"{authInfo.AadInstance}{authInfo.TenantId}", // 明确拼接授权地址
                RequireHttpsMetadata = true, // 生产环境建议启用HTTPS验证
                Scope = "openid email profile offline_access user.read",
                SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                RedeemCode = true,
                ResponseType = OpenIdConnectResponseType.CodeIdToken,
                TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = false // 仅测试用,生产环境建议开启发行人验证
                },
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    AuthorizationCodeReceived = async (notification) =>
                    {
                        var idClient = ConfidentialClientApplicationBuilder.Create(authInfo.ClientId)
                            .WithRedirectUri(authInfo.RedirectUri)
                            .WithClientSecret(authInfo.AppSecret)
                            .WithAuthority(authInfo.Authority)
                            .Build();

                        string[] scopes = new[] { "user.read" };

                        try
                        {
                            AuthenticationResult result = await idClient.AcquireTokenByAuthorizationCode(
                                scopes, notification.Code)
                            .WithTenantId(authInfo.TenantId)
                            .ExecuteAsync();

                            var token = result.AccessToken;
                        }
                        catch (MsalUiRequiredException ex)
                        {
                            // 捕获需要交互式操作的异常,提取Claims
                            if (!string.IsNullOrEmpty(ex.Claims))
                            {
                                notification.OwinContext.Session["MsalClaims"] = ex.Claims;
                            }
                            // 触发重新登录,引导用户完成权限同意或条件验证
                            notification.OwinContext.Authentication.Challenge(
                                new AuthenticationProperties { RedirectUri = "/" }, 
                                authInfo.AuthenticationType);
                        }
                        catch (Exception ex)
                        {
                            throw;
                        }
                    },
                    RedirectToIdentityProvider = (notification) =>
                    {
                        // 检查会话中是否有需要传递的Claims
                        var claims = notification.OwinContext.Session["MsalClaims"] as string;
                        if (!string.IsNullOrEmpty(claims))
                        {
                            // 将Claims添加到授权请求参数中
                            notification.ProtocolMessage.SetParameter("claims", claims);
                            // 清除会话中的Claims
                            notification.OwinContext.Session.Remove("MsalClaims");
                        }
                        return Task.FromResult(0);
                    }
                },
            });

            app.UseStageMarker(PipelineStage.Authenticate);
        }
    }
}

关键说明

  • 捕获MsalUiRequiredException时,提取ex.Claims存入会话,再触发重新认证;
  • 在RedirectToIdentityProvider中,将Claims参数注入授权请求,引导用户完成权限同意和条件访问验证;
  • 确保Azure AD应用注册中的RedirectUri与代码配置完全一致,否则会导致认证失败;
  • 若为管理员同意权限,需先由租户管理员在Azure AD应用权限页点击“授予管理员同意”。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:50:53