WPF中Azure B2C MFA验证失败时,如何获取详细异常信息?
现有代码
令牌获取扩展方法
public static async Task<AuthenticationResult> AcquireTokenAsync(this IPublicClientApplication publicClientApp) { try { var accounts = await publicClientApp.GetAccountsAsync(); return await Task.Run(() => publicClientApp .AcquireTokenInteractive(apiScopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.ForceLogin) .ExecuteAsync()); } catch (MsalClientException ex) { if (ex.ErrorCode.Equals("authentication_canceled")) { log.Info("Authentication was cancelled"); } log.Error("Error in login sequence", ex); throw; } }
PublicClientApplication 创建代码
app = PublicClientApplicationBuilder.Create(ClientId) .WithB2CAuthority(Authority) .Build();
当前场景与问题
目前已能捕获用户取消认证窗口的场景(对应错误码authentication_canceled的MsalClientException),但当用户多次MFA验证失败后,AcquireTokenInteractive抛出的是错误码为authentication_ui_failed的MsalClientException,异常信息仅显示:
Microsoft.Identity.Client.MsalClientException:
The browser based authentication dialog failed to complete. Reason: The server encountered an unexpected condition that prevented it from fulfilling the request.
但通过Fiddler抓包可以看到Azure B2C返回的明确错误:
status=500 Internal Server Error
error=server_error
error_description=AADB2C90151: User has exceeded the maximum number for retries for multi-factor authentication.
提问:是否可以从AcquireTokenInteractive抛出的异常中直接获取这类详细的B2C错误信息?
解决方案
1. 启用MSAL详细日志
通过配置MSAL日志,可以捕获到B2C返回的原始错误信息。在创建PublicClientApplication时添加日志配置:
app = PublicClientApplicationBuilder.Create(ClientId) .WithB2CAuthority(Authority) .WithLogging((level, message, containsPii) => { if (level == LogLevel.Error) { log.Error($"MSAL Error Detail: {message}"); } }, LogLevel.Error) .Build();
日志中会包含B2C返回的完整错误描述,包括AADB2C90151这类具体错误码。
2. 扩展异常捕获范围与逻辑
MSAL在交互式认证流程中,部分B2C服务器错误可能被包装为MsalClientException或MsalServiceException,需要扩展捕获逻辑并提取错误信息:
public static async Task<AuthenticationResult> AcquireTokenAsync(this IPublicClientApplication publicClientApp) { try { var accounts = await publicClientApp.GetAccountsAsync(); // 移除不必要的Task.Run包装,ExecuteAsync本身就是异步方法 return await publicClientApp .AcquireTokenInteractive(apiScopes) .WithAccount(accounts.FirstOrDefault()) .WithPrompt(Prompt.ForceLogin) .ExecuteAsync(); } catch (MsalClientException ex) { if (ex.ErrorCode.Equals("authentication_canceled")) { log.Info("Authentication was cancelled"); } else if (ex.ErrorCode.Equals("authentication_ui_failed")) { // 从异常消息中提取B2C具体错误码 if (ex.Message.Contains("AADB2C90151")) { log.Info("User has exceeded the maximum number of MFA retries"); } log.Error("Authentication UI failed", ex); } else { log.Error("Client-side authentication error", ex); } throw; } catch (MsalServiceException ex) { // 直接捕获B2C服务器返回的错误码 if (ex.ErrorCode.StartsWith("AADB2C")) { log.Info($"B2C Server Error: {ex.ErrorCode} - {ex.Message}"); } log.Error("B2C service authentication error", ex); throw; } catch (Exception ex) { log.Error("Unexpected authentication error", ex); throw; } }
3. 额外提示
- 避免用
Task.Run包装异步方法ExecuteAsync,这会导致不必要的线程切换,降低性能。 - 若异常被包装在
AggregateException中,可通过ex.InnerExceptions遍历提取详细错误信息。
内容的提问来源于stack exchange,提问作者RollerMobster

