Repository navigation
Record and classify Entra MFA broker failures instead of showing one line and logging nothing (#3196) - #3201
Conversation
…line and logging nothing (#3196) Adding an Azure SQL Database server with Microsoft Entra MFA can fail with a WAM broker error the process never records anywhere: AddServerDialog's connection test catches the exception, keeps Message, and never calls AppLogger, so the dialog text is the only artefact of the failure. Classify the failure by which stage of the broker handshake it died at, log the whole exception chain, and tell the user where the log is.
|
Reviewed the diff (Lite.Tests/EntraBrokerFailureTests.cs, Lite/Helpers/ConnectionFailureMessage.cs, Lite/Services/ServerManager.cs, Lite/Windows/AddServerDialog.xaml.cs, PerformanceMonitor.Common/Helpers/EntraBrokerFailure.cs). Correctness — logic checks out: Lite/Darling parity — no drift. The PR body's claim that Darling needs no equivalent checks out: Security — no concerns. The log line names the selected auth mode via radio-button state rather than the connection string, so no password/secret reaches the log. This is a local desktop-app change with no network/SQL surface. Performance — negligible; this only touches the failure path. Nothing else stood out — the classifier's marker ordering, the |
The remedies are documented; how often they work is not something this can know, and a dialog is a poor place to guess it.
The block read "Not gated on mfaCancelled" directly above if (!mfaCancelled). The gating is deliberate and the reason was right; only the first three words were wrong.
ReviewWent through the diff ( Correctness
Lite/Darling parity
Security
Tests
Nothing blocking found. One very minor, non-blocking observation: |
Adding an Azure SQL Database server with Microsoft Entra MFA can fail with a Windows account-broker error that the process records nowhere.
AddServerDialog.RunConnectionTestAsynccatches the exception, keepsex.Message, and never callsAppLogger— there is noAppLoggerreference in that file at all — so the dialog text is the only artefact the failure leaves behind. The reporter of #3196 hit exactly that, went looking for a log, and found neither the data directory nor a logs directory under it.This does not fix the authentication failure. It cannot: see the mechanism below, and see the note about verification.
What the driver actually does, and why there is no fallback to reach for
Read off
Microsoft.Data.SqlClient7.0.2,Microsoft.Identity.Client4.84.2,Microsoft.Identity.Client.Broker4.84.2 andmsalruntime0.20.6 — all four are what the lockfile resolves for Lite.ActiveDirectoryAuthenticationProviderOptions.UseWamBrokerlooks like an opt-out and is not one. The provider computes:That GUID is the driver's own first-party Entra application id and it is the field's initializer, so an application that does not supply an
ApplicationClientId— which is every caller of the parameterlessnew ActiveDirectoryAuthenticationProvider(), includingEntraInteractiveAuth— pins the broker on and makes the flag inert. The driver's own doc comment agrees: "When null, the SqlClient first-party application id is used and WAM broker mode is forced on (regardless ofUseWamBroker)."MSAL falls back to a browser only when the broker is unavailable —
IsBrokerInstalledAndInvokablereturns false for an unsupported OS, a non-AAD authority, or a failedmsalruntimeinit. An invoked broker that returns an error is propagated, not retried another way. So a broker refusal has no in-process remedy.The broker's own explanation of why it refused is also unreachable.
msalruntimeredacts it to the literal stringContext: (pii)unless PII is enabled, MSAL only enables it vias_lazyCore.Value.EnablePii(_logger.PiiLoggingEnabled), and SqlClient'sCreateClientAppInstancenever callsWithLogging. There is no seam to turn it on from here.What is left is the stage the handshake died at, which the error text does settle — and the stage is the difference between a bug in this application and a condition on the user's machine:
WindowHandleMissingwindow_handle_requiredIntPtr.ZeroreachedWithParentActivityOrWindowBrokerUnavailablewam_runtime_init_failedmsalruntimedid not loadBrokerRejectedunknown_broker_error,WAM_provider_error_*That table is also why #2184 is not the defect here.
RuntimeBroker.AcquireTokenInteractiveAsyncthrowswindow_handle_requiredwhen_parentHandle == IntPtr.Zero, and the reported error isunknown_broker_error— so the handle was non-zero and #2184's plumbing is working. Two further checks agree:v3.6.0(the reported version) containsEntraInteractiveAuth.cs, and MSAL'sWithParentActivityOrWindowFuncinvokes the callback perAcquireTokenInteractiverather than capturing it atBuild(), so a cachedIPublicClientApplicationcannot hand the broker a stale HWND.The change
EntraBrokerFailure(shared) classifies a failure by stage, walking the whole inner-exception chain — the driver wraps MSAL's exception, which wraps the broker's, andMessageis the outermost layer only. Matching is on the rendered text rather than on a typedMsalException, so Lite is not pinned to whichever MSAL version the driver happens to carry; the codes travel in the text because SqlClient interpolates them asError code 0x<code>.ConnectionFailureMessagecomposes the dialog body: the driver's error first (it carries the codes a report needs), then what stage it came from, then the resolved log directory. The log location is in the dialog because a path like%LOCALAPPDATA%\PerformanceMonitorLite-Data\logsis not guessable from a dialog that does not name it.AddServerDialoglogs the exception throughAppLogger.Error, which already walks the inner chain recursively — the call was simply missing. Cancellation is excluded: it is a decision the user made and has its own dialog, so logging it as an error would file user intent as a fault. The log line names the selected authentication mode, taken from the radio buttons rather than read back off the connection string, because the connection string holds a password.ServerManager.CheckServerConnectivity'sSqlExceptionarm now passes the exception rather thanex.Message, matching the generic arm beside it. That is the path a saved Entra MFA server hits on every collection cycle, and aSqlExceptionis the shape this failure takes.Verification
Not verified against a live Entra-MFA tenant. It cannot be from here — reproducing an interactive Entra failure needs a real tenant and a Windows host, which is the same limit #2184 had (its fix was confirmed by that issue's reporter, not by CI). The classifier's doc comment says so in the file. What is verified is the mechanism, read off the four packages above, and the classification boundaries, pinned by 21 tests.
Every pin was red-proved by mutation, restored, and re-confirmed against a
Total: 21, Failed: 0, Not Run: 0baseline:MessagebrokerExplainreturns null forBrokerUnavailableComposenever appends the driver errorAppLogger.Errorcall deleted, its comment keptThe last one needed its instrument mutated too.
ConnectionTest_WritesTheExceptionToTheLogasserts a call site in source, because thecatchlives in a WPFWindowthat behavioural coverage cannot reach — and the block carries a comment namingAppLogger.Error. With the call deleted andCSharpSourceWalker.StripCommentsAndStringsremoved from the pin, the pin passes: Failed: 0. The strip is what makes it a check on code rather than on my own comment.Service Brokeris in the negative cases deliberately: SQL Server's own feature shares the word and nothing else, and a substring match onbrokertells a user their Windows account is broken when a queue is disabled.SKU parity
Darling needs no equivalent. Its Viewer rejects Entra MFA outright at the credential step (
ServerStoreCredential.UnsupportedAuthMessage,AddServerDialog.xaml.cs:340— "no Darling service connect path"), so there is no interactive-auth seam there to keep in parity.EntraBrokerFailurestill lands inPerformanceMonitor.CommonbesideMfaAuthenticationHelper, which is where the existing Entra helper lives.Not done here
Two routes would actually unblock this user class, and both are product decisions rather than bug fixes:
ApplicationClientIdother than the driver's flipsUseWamBrokerto defaultingfalse, which moves the redirect URI tohttp://localhostand the flow to the system browser. It needs a registered multi-tenant Entra application, and a third-party client id may need the user's tenant admin to consent.ActiveDirectoryDefaultreturns fromAcquireTokenAsyncbefore any MSAL public-client application is built, so it never touches WAM at all — it runsDefaultAzureCredential, which picks up anaz login.ActiveDirectoryDeviceCodeFlowalso avoids the broker (MSAL'sDeviceCodeRequestreferences it nowhere) and works with the driver's own client id, needing only a callback to show the code, since the driver's default writes it to a console this app does not have.CHANGELOG
Not edited, per lane convention. Entry text:
AddServerDialog's connection test keptex.Messageand never calledAppLogger, so a failed Entra MFA sign-in left a dialog and no log entry; the reporter of Authentication error connecting to Azure SQL Database. #3196 could not send anything but a screenshot. The failure is now classified by which stage of the WAM broker handshake it died at - the application not supplying a window handle, the broker runtime not loading, or the broker running and refusing - and the whole inner exception chain is logged, since the driver wraps MSAL's exception which wraps the broker's andMessageis only the outermost layer. The dialog names the resolved log directory, because that path is not guessable from a dialog that does not print it.ServerManager'sSqlExceptionarm passes the exception rather than its message, matching the generic arm beside it - that is the path a saved Entra MFA server takes on every collection cycle. The authentication failure itself is not fixed and cannot be from here:Microsoft.Data.SqlClientforces the WAM broker on for any caller using the driver's own Entra application id,UseWamBrokeris inert in that configuration, MSAL falls back to a browser only when the broker is unavailable rather than when an invoked broker refuses, and the broker's stated reason is redacted toContext: (pii)with no seam to enable it. Unverified against a live Entra-MFA tenant, which is the only place this class can be confirmed