ASP.NET Framework直接获取用户委托的Graph访问令牌问题
问题概述
需要实现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的委派权限:
- 如果权限需要管理员同意(如某些敏感权限),需先由租户管理员完成同意操作;
- 如果是用户可同意的权限,确保首次登录时引导用户完成授权同意。
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

