Repository navigation
Add a broker-free Entra sign-in path: ActiveDirectoryDefault returns before the WAM broker is ever built, so it is reachable where Entra MFA is not #3214
Description
Activity
Claude posting for Erik Darling
Three corrections to this issue as I filed it. The central claim survives; two of the three change what the reporter must eventually be told.
1. I verified the wrong population, and so did the check I described as authoritative. This issue quotes
ActiveDirectoryAuthenticationProvider.csfromdotnet/SqlClientatmain. The repository pinsMicrosoft.Data.SqlClient7.0.2. Readingmainfor a claim about a pinned version is exactly the defect this queue has spent the day naming — a figure measured against a population that does not govern.Re-read at tag v7.0.2:
:38is the app-id constant,:63initialises_useWamBrokerfalse,:112is_useWamBroker = _applicationClientId == s_sqlClientApplicationId || options.UseWamBroker. Byte-identical to what is quoted here. So the conclusion holds — but it holds because that file happened not to move, not because the method was sound. Cite the tag, not the branch.2. "Broker-free" is a property of the DEPENDENCY GRAPH, not of the code — and this is a condition on the answer.
Azure.Identity1.18.0 places aBrokerCredentialat the tail ofDefaultAzureCredential's chain, andExcludeBrokerCredentialdefaults to false. The chain is broker-free today only becauseAzure.Identity.Brokeris absent: measured, zero occurrences across every lock file,Directory.Packages.props, and every project file in the repository.Worse than a pinned dependency:
Azure.Identityis not pinned at all. It resolves transitively at 1.18.0 in all seven lock files that carry it, arriving throughMicrosoft.Data.SqlClient.Extensions.Azure's version range. So anyone addingAzure.Identity.Broker— directly or transitively, in any project that reaches Lite — silently reinstates the Windows broker at the end of the very path this issue exists to provide.That belongs in whatever #3196's reporter is eventually told. Telling an outside person "this route does not use the Web Account Manager" is a promise about our dependency graph, and it should be stated as one.
3. The mode is AMBIENT ONLY, not "interactive-or-ambient" as this issue's scope says.
ExcludeInteractiveBrowserCredentialis hard-coded true in the driver, so there is no interactive arm to fall back to. The staged reporter reply already says the option will never show a sign-in prompt, so the text is correct — but it is correct about a constraint this issue mis-scoped, and the reply is currently the only place a reader learns it.The mis-scoping had a real cost: "interactive-or-ambient" is what makes a broker-free mode sound like a replacement for Entra MFA. It is not. It is a way to reuse a sign-in made elsewhere, and a user without one gets a failure rather than a prompt.
Claude posting for Erik Darling
Implemented by #3218, merged to
devas878d091450de. Leaving this issue open until the reporter of #3196 has a build and has reported back — the code exists; whether it works on a live tenant is theirs to confirm, and closing on merge would record a verification nobody performed.Every line number in this issue is wrong, and it is the same error twice. I cited
:272(Default arm returns),:334(public-client app built) and:907(WithBroker) — those aremain's. At the pinned v7.0.2 they are :264, :325 and :826. I already corrected the branch-versus-tag method above; the numbers it produced were left standing, which is the more quotable half.The ordering argument survives unchanged: 264 < 325. The Default arm still returns before any public-client app exists, so no broker is configured, invoked or consulted.
_useWamBrokerat:112is identical between the two. But cite the tag's numbers, not the branch's.What #3218 established that this issue did not
The control-flow proof is necessary and not sufficient, and this issue does not notice the gap.
Azure.Identity1.18.0 places aBrokerCredentialat the tail ofDefaultAzureCredential's chain, and SqlClient sets onlyExcludeInteractiveBrowserCredential— so the chain genuinely contains a broker link. It is inert solely becauseAzure.Identity.Brokeris absent from our dependency graph. Returning before the public-client app is built does not prevent a broker at the far end of a credential chain, and the proof in this issue would have read as complete. #3219 owns the dependency question; two pins in #3218 guard it, one using the identicalType.GetTypecallAzure.Identity's ownCredentialOptionsMapper.GetBrokerOptionsmakes, so the pin cannot drift from the mechanism it models.And the mode is ambient only —
ExcludeInteractiveBrowserCredential = trueis hard-coded at:869. My "interactive-or-ambient" scoping is corrected above; what follows from it is that the failure message is the deliverable, not a detail, because a user with no ambient sign-in gets a failure rather than a prompt.Worth recording from the implementation
Both broker-absence pins passed vacuously. They ran in a closure with no
Azure.*assemblies on disk, so they asserted nothing — and would have stayed green with the broker package added, which is the exact condition they exist to detect. Found by building a real stand-inAzure.Identity.Brokerassembly and watching them fail. A pin that models a dependency's absence has to be falsified by that dependency's presence, or it is testing its own environment.A classifier's "arms ordered by specificity" claim was not observable at all — the test passed with the arms swapped. Replaced with a two-direction depth assertion that a flattening walk fails.
Darling rejects the mode by construction rather than by omission, established rather than assumed:
MonitoredServerConnectionbuilds only integrated or SQL-login connections and acquires no tokens, andMapAuthis a whitelist ending_ => null, so the mode was already refused on arrival across all three write paths with no edit to that file. No viewer display arm was added, because it would be dead code.Claude posting for Erik Darling
Closing. Implemented and merged in #3218 as
878d091450de:AuthenticationTypes.EntraDefaultCredential, labelled Azure — Existing Sign-In (az login), mapping toSqlAuthenticationMethod.ActiveDirectoryDefaultatLite/Models/ServerConnection.cs:561.I had been holding this open as a placeholder for #3196's reporter to confirm on a live tenant. That is a waiting-on-someone state, not work, and #3196 is already the tracking item for it — keeping a second issue open for the same wait made the queue read as if there were two things to do.
The corrections on this issue stand and are worth reading before anyone quotes it: every line number in the body is
main's rather than v7.0.2's (:264/:325/:826, not:272/:334/:907); the control-flow proof is necessary but not sufficient, becauseAzure.Identityputs aBrokerCredentialat the tail of the chain and returning before the public-client app is built does not prevent that; and the mode is ambient only, not "interactive-or-ambient" as the scope said —ExcludeInteractiveBrowserCredentialis hard-coded true, so a user with no ambient sign-in gets a failure rather than a prompt.Whether it works against a live Entra tenant remains unverified by anyone here, and the reply to #3196's reporter is held behind six conditions until a build carries it and the version can be checked against the artifact rather than a
.csproj.
#3196's reporter cannot sign in with Microsoft Entra MFA and there is nothing they can do about it from the app's side. #3201 made the failure diagnosable; it did not give them a way in. This is the way in.
Why Entra MFA cannot be repaired in place
Read from Microsoft's published source (
dotnet/SqlClient,src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.csatmain), not from a decompiler:The maintainers' own comment on the field: "Always true for the SqlClient first-party app id; for caller-supplied app ids, opt-in via the Options-pattern constructor."
EntraInteractiveAuthcalls the parameterless constructor, so the app id is the driver's own, the==arm is unconditionally true, andUseWamBrokercannot lower it. The Windows Account Manager is mandatory on that path, MSAL falls back to a browser only when the broker is unavailable rather than when an invoked broker refuses, and the refusal reason is redacted toContext: (pii)with no seam to enable it.So the knob exists and is unreachable without shipping our own Entra app registration — a product decision with tenant-consent implications, not a patch.
ActiveDirectoryDefaultnever reaches the broker, and it is provable by control flowIn the same file:
:266-273— theActiveDirectoryDefaultarm resolves aDefaultAzureCredentialand returns at :272.:334—GetPublicClientAppInstanceAsync, the only path to the MSAL public-client app.:907—builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)), reached only from that app's construction.272 is before 334. The Default arm returns before any public-client app exists, so no broker is configured, invoked, or consulted. This is not an inference about MSAL's behaviour — it is the order of two statements in one method.
DefaultAzureCredentialchains credential sources, andExcludeAzureCliCredentialdefaults tofalse, so a developer or DBA who has runaz loginalready has a working path. It also picks up environment, workload-identity and managed-identity credentials without an interactive prompt.What exists today, and what does not
PerformanceMonitor.Common/Models/AuthenticationTypes.csoffers exactly five:Windows,SqlServer,EntraMFA,ServicePrincipal,ManagedIdentity.ActiveDirectoryDefaultandActiveDirectoryDeviceCodeFlowappear nowhere in the repository — zero references in any.csor.xaml. So this is a new option, not a repair of an existing one.ServicePrincipalis already broker-free and is what #3196's reply offers as today's workaround, but it needs someone who can register an application in the tenant and grant it database access. That is a real barrier for a single user evaluating the tool, which is the population #3196 is drawn from.Scope
Add a broker-free interactive-or-ambient Entra option.
ActiveDirectoryDefaultis the one this issue argues for, on the strength of the control-flow proof above.One claim deliberately NOT made:
ActiveDirectoryDeviceCodeFlowwas also suggested as broker-free. Its arm at:403sits after the public-client app is built at:334, so the app it uses may well haveWithBrokerconfigured — the device-code grant not invoking WAM is a claim about MSAL's behaviour that this issue does not establish. If device code flow is wanted, prove that separately rather than inheriting Default's proof.The verification limit, stated up front
This cannot be verified from here. It needs a real Entra tenant and a Windows host. #2184's fix was verified against a live tenant by the reporter of #2184, and #3196's reporter is the same route for this one. So the deliverable is a change whose doc states plainly that it is unverified against a live tenant, plus something the reporter can actually run — not a fix asserted to work.
Two-SKU parity applies: Darling's viewer rejects
EntraMFA,ServicePrincipalandManagedIdentityoutright viaServerStoreCredential.UnsupportedAuthMessage, so establish whether a new mode belongs there before assuming it does.