处理 MSAL.NET 中的错误和异常Handle errors and exceptions in MSAL.NET

本文概述不同类型的错误,并提供有关处理常见登录错误的建议。This article gives an overview of the different types of errors and recommendations for handling common sign-in errors.

MSAL 错误处理基础知识MSAL error handling basics

Microsoft 身份验证库 (MSAL) 中的异常旨在帮助应用开发人员进行故障排除,而不是用于显示给最终用户。Exceptions in Microsoft Authentication Library (MSAL) are intended for app developers to troubleshoot, not for displaying to end users. 异常消息未经本地化。Exception messages are not localized.

处理异常和错误时,可以使用异常类型本身和错误代码来区分不同的异常。When processing exceptions and errors, you can use the exception type itself and the error code to distinguish between exceptions. 有关错误代码的列表,请参阅 Azure AD 身份验证和授权错误代码For a list of error codes, see Azure AD Authentication and authorization error codes.

在登录体验期间,可能会遇到有关许可、条件访问(MFA、设备管理、基于位置的限制)、令牌颁发和兑换以及用户属性的错误。During the sign-in experience, you may encounter errors about consents, Conditional Access (MFA, Device Management, Location-based restrictions), token issuance and redemption, and user properties.

以下部分提供了有关应用的错误处理的更多详细信息。The following section provides more details about error handling for your app.

MSAL.NET 中的错误处理Error handling in MSAL.NET

处理 .NET 异常时,可以使用异常类型本身和 ErrorCode 成员来区分不同的异常。When processing .NET exceptions, you can use the exception type itself and the ErrorCode member to distinguish between exceptions. ErrorCode 的值是 MsalError 类型的常量。ErrorCode values are constants of type MsalError.

也可以查看 MsalClientExceptionMsalServiceExceptionMsalUIRequiredException 字段。You can also have a look at the fields of MsalClientException, MsalServiceException, and MsalUIRequiredException.

如果引发 MsalServiceException,请尝试查看身份验证和授权错误代码,以查看其中是否列出了相关代码。If MsalServiceException is thrown, try Authentication and authorization error codes to see if the code is listed there.

常见的 .NET 异常Common .NET exceptions

下面是可能引发的常见异常和一些可能的缓解措施:Here are the common exceptions that might be thrown and some possible mitigations:

异常Exception 错误代码Error code 缓解措施Mitigation
MsalUiRequiredExceptionMsalUiRequiredException AADSTS65001:用户或管理员尚未许可使用名为“{appName}”、ID 为“{appId}”的应用程序。AADSTS65001: The user or administrator has not consented to use the application with ID '{appId}' named '{appName}'. 针对此用户和资源发送交互式授权请求。Send an interactive authorization request for this user and resource. 首先获取用户同意。Get user consent first. 如果未使用 .NET Core(它没有任何 Web UI),请调用 AcquireTokeninteractive(仅一次)。If you aren't using .NET Core (which doesn't have any Web UI), call (once only) AcquireTokeninteractive. 如果使用 .NET Core 或者不希望执行 AcquireTokenInteractive,则用户可以导航到某个 URL 来提供许可:https://login.partner.microsoftonline.cn/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.readIf you are using .NET core or don't want to do an AcquireTokenInteractive, the user can navigate to a URL to give consent: https://login.partner.microsoftonline.cn/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read. 要调用 AcquireTokenInteractive,请使用 app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();to call AcquireTokenInteractive: app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();
MsalUiRequiredExceptionMsalUiRequiredException AADSTS50079:用户必须使用多重身份验证 (MFA)AADSTS50079: The user is required to use multi-factor authentication (MFA). 无缓解措施。There is no mitigation. 如果已为租户配置 MFA 并且 Azure Active Directory (AAD) 决定对其进行强制实施,则回退到 AcquireTokenInteractiveAcquireTokenByDeviceCode 等交互式流。If MFA is configured for your tenant and Azure Active Directory (AAD) decides to enforce it, fall back to an interactive flow such as AcquireTokenInteractive or AcquireTokenByDeviceCode.
MsalServiceExceptionMsalServiceException AADSTS90010:/common 或 /consumers 终结点不支持此授权类型 。AADSTS90010: The grant type isn't supported over the /common or /consumers endpoints. 请使用 /organizations 或特定于租户的终结点。Use the /organizations or tenant-specific endpoint. 使用了 /commonYou used /common. 根据 Azure AD 发出的消息中所述,颁发机构需要使用一个租户或 /organizationsAs explained in the message from Azure AD, the authority needs to have a tenant or otherwise /organizations.
MsalServiceExceptionMsalServiceException AADSTS70002:请求正文必须包含以下参数:client_secret or client_assertionAADSTS70002: The request body must contain the following parameter: client_secret or client_assertion. 如果应用程序未注册为 Azure AD 中的公共客户端应用程序,则可能引发此异常。This exception can be thrown if your application was not registered as a public client application in Azure AD. 在 Azure 门户中编辑应用程序的清单,并将 allowPublicClient 设置为 trueIn the Azure portal, edit the manifest for your application and set allowPublicClient to true.
MsalClientExceptionMsalClientException unknown_user Message:无法识别已登录的用户unknown_user Message: Could not identify logged in user 库无法查询当前的 Windows 已登录用户,或者此用户未加入 AD 或 Azure AD(已加入工作区的用户不受支持)。The library was unable to query the current Windows logged-in user or this user isn't AD or Azure AD joined (work-place joined users aren't supported). 缓解措施 1:在 UWP 中,检查应用程序是否具有以下功能:企业身份验证、专用网络(客户端和服务器)、用户帐户信息。Mitigation 1: on UWP, check that the application has the following capabilities: Enterprise Authentication, Private Networks (Client and Server), User Account Information. 缓解措施 2:实现自己的逻辑以提取用户名(例如 john@contoso.com),并使用 AcquireTokenByIntegratedWindowsAuth 表单来提取用户名。Mitigation 2: Implement your own logic to fetch the username (for example, john@contoso.com) and use the AcquireTokenByIntegratedWindowsAuth form that takes in the username.
MsalClientExceptionMsalClientException integrated_windows_auth_not_supported_managed_userintegrated_windows_auth_not_supported_managed_user 此方法依赖于 Active Directory (AD) 公开的协议。This method relies on a protocol exposed by Active Directory (AD). 如果在 Azure AD 中创建了一个用户但该用户不受 AD 的支持(“托管”用户),则此方法将会失败。If a user was created in Azure AD without AD backing ("managed" user), this method will fail. 在 AD 中创建的受 Azure AD 支持的用户(“联合”用户)可以受益于这种非交互式身份验证方法。Users created in AD and backed by Azure AD ("federated" users) can benefit from this non-interactive method of authentication. 缓解:使用交互式身份验证。Mitigation: Use interactive authentication.

MsalUiRequiredException

调用 AcquireTokenSilent() 时,从 MSAL.NET 返回的其中一个常见状态代码是 MsalError.InvalidGrantErrorOne of common status codes returned from MSAL.NET when calling AcquireTokenSilent() is MsalError.InvalidGrantError. 此状态代码表示应用程序应再次调用身份验证库,但要在交互模式下调用(用于公共客户端应用程序的 AcquireTokenInteractive 或 AcquireTokenByDeviceCodeFlow 会在 Web 应用中执行质询)。This status code means that the application should call the authentication library again, but in interactive mode (AcquireTokenInteractive or AcquireTokenByDeviceCodeFlow for public client applications, do have a challenge in Web apps). 这是因为在颁发身份验证令牌之前,需要进行其他用户交互。This is because additional user interaction is required before authentication token can be issued.

大多数情况下,AcquireTokenSilent 失败的原因是,令牌缓存没有与请求匹配的令牌。Most of the time when AcquireTokenSilent fails, it is because the token cache doesn't have tokens matching your request. 访问令牌将在 1 小时后过期,AcquireTokenSilent 将尝试基于刷新令牌获取新的令牌(在 OAuth2 术语中,这是刷新令牌流)。Access tokens expire in 1 hour, and AcquireTokenSilent will try to fetch a new one based on a refresh token (in OAuth2 terms, this is the "Refresh Token' flow). 此流也可能因各种原因而失败,例如,如果租户管理员配置了更严格的登录策略。This flow can also fail for various reasons, for example if a tenant admin configures more stringent login policies.

交互的目标在于让用户执行一项操作。The interaction aims at having the user do an action. 在这些条件中,有些对于用户来说很容易解决(例如,通过单击接受使用条款),而有些则无法通过当前配置进行解决(例如,有问题的计算机需要连接到特定的公司网络)。Some of those conditions are easy for users to resolve (for example, accept Terms of Use with a single click), and some can't be resolved with the current configuration (for example, the machine in question needs to connect to a specific corporate network). 有些条件可帮助用户设置多重身份验证,或者在其设备上安装 Microsoft Authenticator。Some help the user setting-up multi-factor authentication, or install Microsoft Authenticator on their device.

MsalUiRequiredException 分类枚举MsalUiRequiredException classification enumeration

MSAL 公开一个 Classification 字段,你可以读取该字段,以便提供更好的用户体验。MSAL exposes a Classification field, which you can read to provide a better user experience. 例如,用于告知用户其密码已过期,或者他们需要许可使用某些资源。For example to tell the user that their password expired or that they'll need to provide consent to use some resources. 支持的值是 UiRequiredExceptionClassification 枚举的一部分:The supported values are part of the UiRequiredExceptionClassification enum:

分类Classification 含义Meaning 建议的处理方式Recommended handling
BasicActionBasicAction 在交互式身份验证流中,条件可以通过用户交互来解决。Condition can be resolved by user interaction during the interactive authentication flow. 调用 AcquireTokenInteractively()。Call AcquireTokenInteractively().
AdditionalActionAdditionalAction 在交互式身份验证流之外,条件可以通过与系统进行其他补救交互来解决。Condition can be resolved by additional remedial interaction with the system, outside of the interactive authentication flow. 调用 AcquireTokenInteractively() 以显示一条说明补救操作的消息。Call AcquireTokenInteractively() to show a message that explains the remedial action. 如果用户不太可能完成补救操作,则调用应用程序可能会选择隐藏需要 additional_action 的流。Calling application may choose to hide flows that require additional_action if the user is unlikely to complete the remedial action.
MessageOnlyMessageOnly 条件目前无法解决。Condition can't be resolved at this time. 启动交互式身份验证流将显示一条说明条件的消息。Launching interactive authentication flow will show a message explaining the condition. 调用 AcquireTokenInteractively() 以显示一条说明条件的消息。Call AcquireTokenInteractively() to show a message that explains the condition. 用户读取该消息并关闭窗口后,AcquireTokenInteractively() 将返回 UserCanceled 错误。AcquireTokenInteractively() will return UserCanceled error after the user reads the message and closes the window. 如果用户不太可能从该消息中获益,则调用应用程序可能会选择隐藏导致 message_only 的流。Calling application may choose to hide flows that result in message_only if the user is unlikely to benefit from the message.
ConsentRequiredConsentRequired 用户许可缺失或已撤销。User consent is missing, or has been revoked. 调用 AcquireTokenInteractively(),以取得用户许可。Call AcquireTokenInteractively() for user to give consent.
UserPasswordExpiredUserPasswordExpired 用户的密码已过期。User's password has expired. 调用 AcquireTokenInteractively(),以便用户可以重置其密码。Call AcquireTokenInteractively() so that user can reset their password.
PromptNeverFailedPromptNeverFailed 通过参数 prompt=never 调用交互式身份验证,强制 MSAL 依赖浏览器 cookie,而不是显示浏览器。Interactive Authentication was called with the parameter prompt=never, forcing MSAL to rely on browser cookies and not to display the browser. 此操作已失败。This has failed. 在没有 Prompt.None 的情况下,调用 AcquireTokenInteractively()Call AcquireTokenInteractively() without Prompt.None
AcquireTokenSilentFailedAcquireTokenSilentFailed MSAL SDK 没有足够的信息,无法从缓存中获取令牌。MSAL SDK doesn't have enough information to fetch a token from the cache. 这可能是因为缓存中没有令牌,或找不到帐户。This can be because no tokens are in the cache or an account wasn't found. 此错误消息包含更多详细信息。The error message has more details. 调用 AcquireTokenInteractively()。Call AcquireTokenInteractively().
None 未提供更多详细信息。No further details are provided. 在交互式身份验证流中,条件可以通过用户交互来解决。Condition may be resolved by user interaction during the interactive authentication flow. 调用 AcquireTokenInteractively()。Call AcquireTokenInteractively().

.NET 代码示例.NET code example

AuthenticationResult res;
try
{
 res = await application.AcquireTokenSilent(scopes, account)
        .ExecuteAsync();
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
 switch (ex.Classification)
 {
  case UiRequiredExceptionClassification.None:
   break;
  case UiRequiredExceptionClassification.MessageOnly:
  // You might want to call AcquireTokenInteractive(). Azure AD will show a message
  // that explains the condition. AcquireTokenInteractively() will return UserCanceled error
  // after the user reads the message and closes the window. The calling application may choose
  // to hide features or data that result in message_only if the user is unlikely to benefit 
  // from the message
  try
  {
      res = await application.AcquireTokenInteractive(scopes).ExecuteAsync();
  }
  catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
  {
   // Do nothing. The user has seen the message
  }
  break;

  case UiRequiredExceptionClassification.BasicAction:
  // Call AcquireTokenInteractive() so that the user can, for instance accept terms
  // and conditions

  case UiRequiredExceptionClassification.AdditionalAction:
  // You might want to call AcquireTokenInteractive() to show a message that explains the remedial action. 
  // The calling application may choose to hide flows that require additional_action if the user 
  // is unlikely to complete the remedial action (even if this means a degraded experience)

  case UiRequiredExceptionClassification.ConsentRequired:
  // Call AcquireTokenInteractive() for user to give consent.
  
  case UiRequiredExceptionClassification.UserPasswordExpired:
  // Call AcquireTokenInteractive() so that user can reset their password
  
  case UiRequiredExceptionClassification.PromptNeverFailed:
  // You used WithPrompt(Prompt.Never) and this failed
  
  case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
  default:
  // May be resolved by user interaction during the interactive authentication flow.
  res = await application.AcquireTokenInteractive(scopes)
                         .ExecuteAsync(); break;
 }
}

条件访问和声明质询Conditional access and claims challenges

以无提示方式获取令牌时,如果你尝试访问的 API 需要条件访问声明质询(例如 MFA 策略),则应用程序可能会收到错误。When getting tokens silently, your application may receive errors when a Conditional Access claims challenge such as MFA policy is required by an API you're trying to access.

处理此错误的模式是使用 MSAL 以交互方式获取令牌。The pattern for handling this error is to interactively acquire a token using MSAL. 这会提示用户,并为他们提供机会来满足所需的条件访问策略。This prompts the user and gives them the opportunity to satisfy the required Conditional Access policy.

在某些情况下调用需要条件访问的 API 时,API 返回的错误中可能会包含声明质询。In certain cases when calling an API requiring Conditional Access, you can receive a claims challenge in the error from the API. 例如,如果条件访问策略要求使用托管设备 (Intune),则错误将类似于 AADSTS53000:需要管理你的设备才能访问此资源For instance if the Conditional Access policy is to have a managed device (Intune) the error will be something like AADSTS53000: Your device is required to be managed to access this resource or something similar. 在这种情况下,可以在令牌获取调用中传递声明,使系统提示用户,以满足相应的策略。In this case, you can pass the claims in the acquire token call so that the user is prompted to satisfy the appropriate policy.

从 MSAL.NET 调用需要条件访问的 API 时,应用程序需要处理声明质询异常。When calling an API requiring Conditional Access from MSAL.NET, your application will need to handle claim challenge exceptions. 此错误将显示为 MsalServiceException,其中的 Claims 属性不为空。This will appear as an MsalServiceException where the Claims property won't be empty.

要处理声明质询,需要使用 PublicClientApplicationBuilder 类的 .WithClaim() 方法。To handle the claim challenge, you'll need to use the .WithClaim() method of the PublicClientApplicationBuilder class.

出现错误和异常后重试Retrying after errors and exceptions

在调用 MSAL 时,应实现自己的重试策略。You're expected to implement your own retry policies when calling MSAL. MSAL 会对 Azure AD 服务进行 HTTP 调用,并且有时会发生失败。MSAL makes HTTP calls to the Azure AD service, and occasionally failures can occur. 例如,网络可能出现故障,或者服务器发生过载。For example the network can go down or the server is overloaded.

HTTP 429HTTP 429

如果过多的请求导致服务令牌服务器 (STS) 过载,Retry-After 响应字段中将返回 HTTP 错误 429,并提示可在多长时间后重试。When the Service Token Server (STS) is overloaded with too many requests, it returns HTTP error 429 with a hint about how long until you can try again in the Retry-After response field.

HTTP 错误代码 500-600HTTP error codes 500-600

对于 HTTP 错误代码为 500-600 的错误,MSAL.NET 实现一个简单的重试一次机制。MSAL.NET implements a simple retry-once mechanism for errors with HTTP error codes 500-600.

MsalServiceExceptionSystem.Net.Http.Headers.HttpResponseHeaders 作为 namedHeaders 属性展现。MsalServiceException surfaces System.Net.Http.Headers.HttpResponseHeaders as a property namedHeaders. 可以利用错误代码中的附加信息来提高应用程序的可靠性。You can use additional information from the error code to improve the reliability of your applications. 对于前面所述的情况,可以使用 RetryConditionHeaderValue 类型的 RetryAfterproperty 并计算重试时间。In the case described, you can use the RetryAfterproperty (of type RetryConditionHeaderValue) and compute when to retry.

下面是使用客户端凭据流的守护程序示例。Here is an example for a daemon application using the client credentials flow. 可以将此流修改为使用任何用于获取令牌的方法。You can adapt this to any of the methods for acquiring a token.


bool retry = false;
do
{
    TimeSpan? delay;
    try
    {
         result = await publicClientApplication.AcquireTokenForClient(scopes, account).ExecuteAsync();
    }
    catch (MsalServiceException serviceException)
    {
         if (serviceException.ErrorCode == "temporarily_unavailable")
         {
             RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter;
             if (retryAfter.Delta.HasValue)
             {
                 delay = retryAfter.Delta;
             }
             else if (retryAfter.Date.HasValue)
             {
                 delay = retryAfter.Date.Value.Offset;
             }
         }
    }
    // . . .
    if (delay.HasValue)
    {
        Thread.Sleep((int)delay.Value.TotalMilliseconds); // sleep or other
        retry = true;
    }
} while (retry);

后续步骤Next steps

请考虑启用 MSAL.NET 中的日志,以帮助诊断并调试问题。Consider enabling Logging in MSAL.NET to help you diagnose and debug issues.