Skip to content

Make authentication provider discovery AOT-safe - #4573

Draft
paulmedynski wants to merge 1 commit into
dev/paul/auth-registry-abstractionsfrom
dev/paul/aot-azure-fix
Draft

paulmedynski wants to merge 1 commit into
dev/paul/auth-registry-abstractionsfrom
dev/paul/aot-azure-fix

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Makes SqlClient's authentication provider discovery safe for NativeAOT.

Stacked on #4670, which moves the provider registry into Abstractions and removes the Abstractions→SqlClient reflection. This PR handles the other reflection edge: SqlClient→Azure extension discovery.

The problem

AuthenticationBootstrapper discovers app.config providers and the Azure extension via Type.GetType, Assembly.Load, and Activator.CreateInstance. Under NativeAOT the trimmer has no static reference to follow, so it trims the Azure extension and every Active Directory authentication method silently loses its provider (#4193). None of the reflection carried trim annotations, so the compiler emitted no warnings.

The change

  • New EnableReflectionBasedAuthenticationProviderDiscovery app context switch (default true), guarding the bootstrapper's discovery. On .NET 9+ it carries [FeatureSwitchDefinition]; ILLink.Substitutions.xml provides the same substitution on .NET 8.
  • [RequiresUnreferencedCode] / [RequiresDynamicCode] on LoadConfiguration and LoadAzureExtensionProvider, so trim analysis warns even when the switch is left enabled.
  • tools/AotCompatibility — a standalone publish harness that verifies the substitution actually removes LoadAzureExtensionProvider from the native image.

An AOT app opts in at publish time:

<RuntimeHostConfigurationOption
    Include="Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery"
    Value="false"
    Trim="true" />

The trimmer folds the guard to a constant false, eliminates the dead branch, and removes the reachable reflection code. The app then registers providers explicitly with SqlAuthenticationProvider.SetProvider.

Also: UseManagedNetworking split

UseManagedNetworking mixed the OS check with the switch read, so the existing SNI substitution could only be embedded conditionally. Hoisting the platform guard out:

public static bool UseManagedNetworking =>
    !OperatingSystem.IsWindows() || UseManagedNetworkingOnWindows;

leaves UseManagedNetworkingOnWindows as a pure substitution target. The guard makes the substitution safe on every platform, so ILLink.Substitutions.xml is now unconditional for .NET.

Zero-code-change AOT is not achievable

One line of startup registration is required — the trimmer has no reason to keep ActiveDirectoryAuthenticationProvider without a static reference from the app. Rejected alternatives: [ModuleInitializer] in the extension (initializers of trimmed assemblies don't run — same chicken-and-egg), a source generator (viable but far more to ship and maintain), and a root descriptor XML (preserves the type but leaves the Assembly.Load path intact, which defeats the point). This matches the established .NET pattern, e.g. JsonSerializerContext registration under AOT.

API changes

None. The switch is internal; the trimmer contract is the documented RuntimeHostConfigurationOption name.

Issues

Fixes #4193 (together with #4670).

Testing

  • New ILLinkSubstitutionsTests.cs — asserts the substitution XML targets property getters that actually exist, so a rename can't silently break trimming.
  • Updated LocalAppContextSwitchesTest.cs / LocalAppContextSwitchesHelper.cs for the new switch and the s_useManagedNetworkingOnWindows rename.
  • New tools/AotCompatibility — publishes a NativeAOT app and inspects the output for LoadAzureExtensionProvider, confirming the feature switch removes it. See its README for expected output in both configurations.

Validation on this branch:

  • Microsoft.Data.SqlClient + UnitTests build clean for net9.0, 0 warnings.
  • Unit tests: 1188 passed, 14 skipped. The single failure (SimulatedServerTests.ConnectionTests.IntegratedAuthConnectionTest, "Cannot generate SSPI context") is pre-existing and environmental — no Kerberos on the Linux test host.

Conflict resolutions worth reviewing

Replaying onto current main required resolving conflicts introduced by work that landed after the AOT branch point:

  • LocalAppContextSwitches.cs — kept the AOT two-property design and the s_useManagedNetworkingOnWindows field; retained main's default-value documentation.
  • LocalAppContextSwitchesHelper.cs — kept main's GetLocalAppContextSwitchesType() refactor and runtime OS check, adopting the AOT field rename.
  • LocalAppContextSwitchesTest.cs — kept main's switchesHelper assertions (including UseLegacyIdleTimeoutBehavior) and added the new switch assertion.

Stack

#4566 assembly-signing-sqlserver
  #4567 assembly-signing-logging
    #4568 assembly-signing-abstractions
      #4569 assembly-signing-sqlclient
        #4570 assembly-signing-azure
          #4670 auth-registry-abstractions
            THIS PR  aot-azure-fix

Previously this PR combined the registry relocation with the AOT work. The structural refactor is now split out into #4670 and this PR contains only the AOT changes. Supersedes the AOT content of #4348.

Copilot AI lite review requested due to automatic review settings August 21, 2026 16:00
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replays and updates the AOT-/trimmer-safe authentication provider discovery work on top of the newer assembly-signing stack by (1) moving the auth-provider registry into the Abstractions assembly, (2) isolating reflection-based discovery behind a feature switch + trimmer substitutions, and (3) adding/reshaping tests and an AOT compatibility tool to validate trimming behavior.

Changes:

  • Introduces AuthenticationBootstrapper to lazily seed providers from app.config and the optional Azure extension, gated by Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery for AOT/trimming.
  • Moves the provider registry into Microsoft.Data.SqlClient.Extensions.Abstractions and rewires SqlAuthenticationProvider.GetProvider/SetProvider to use it directly (removing the Abstractions→SqlClient reflection bridge).
  • Adds/updates unit tests and a tools/AotCompatibility app to validate NativeAOT publish and trimming of the reflection-based discovery path.

Reviewed changes

Copilot reviewed 53 out of 54 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/AotCompatibility/README.md Documents what the AOT compatibility tool validates and how to run/publish it.
tools/AotCompatibility/Program.cs Implements runtime checks and ILC map-file verification for trimming behavior.
tools/AotCompatibility/Directory.Packages.props Enables central package management for the tool without specifying versions (project-mode).
tools/AotCompatibility/Directory.Build.props Prevents inheriting repo-wide Directory.Build.props settings for the tool.
tools/AotCompatibility/AotCompatibility.csproj Defines the AOT/trimming validation app and sets the runtime host configuration option for the feature switch.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs Removes legacy manager-based tests replaced by bootstrapper/registry coverage.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs Asserts the new internal reflection-discovery switch default directly.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ILLinkSubstitutionsTests.cs Adds tests ensuring the unified ILLink substitution resource is embedded on non-net462 TFMs.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/DummySqlAuthenticationProvider.cs Adds a dummy provider used by net462 UnitTests app.config-based registration tests.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/AuthenticationBootstrapperTests.cs Adds core bootstrapper tests (Azure extension absent) covering config and constructor-selection logic via stubs.
src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj Ensures net462 app.config is copied to output for config-driven provider tests.
src/Microsoft.Data.SqlClient/tests/UnitTests/app.config Adds UnitTests app.config configuring a dummy provider and bootstrapper settings for net462 tests.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs Removes legacy functional tests tied to the old manager/app.config flow.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj Removes copying FunctionalTests app.config for net462 (tests moved/restructured).
src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs Removes functional-tests dummy provider implementation (test moved/rewired).
src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config Removes functional-tests app.config for dummy provider registration.
src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs Removes dummy-provider registration test from FunctionalTests (moved to Azure.Test/global bootstrap tests).
src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs Updates helper to reflect new cached-field naming and adds helper for the new internal switch.
src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.xml Unifies trimmer substitutions for auth-provider discovery and managed-networking switch in one cross-platform file.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs Adds the new reflection-discovery switch and refactors managed-networking switch into a trimmer substitution target property.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs Ensures authentication bootstrap runs before resolving a provider on the fed-auth path.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs Replaces legacy manager with a lazy bootstrapper that loads config + Azure extension behind a feature switch and AOT annotations.
src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj Embeds the unified ILLink substitution resource for all non-net462 TFMs.
src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs Adds a note clarifying ref assemblies intentionally omit nullable annotations.
src/Microsoft.Data.SqlClient.Extensions/Azure/test/WamBrokerTests.cs Updates comments/collection usage and removes a global-state mutation test that’s no longer needed in this form.
src/Microsoft.Data.SqlClient.Extensions/Azure/test/SqlAuthenticationProviderGlobalCollection.cs Renames the serialized collection definition used for tests mutating the global registry.
src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs Removes legacy “default provider installed” test tied to old manager static-init behavior.
src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperTests.cs Adds Azure-present, non-global bootstrapper tests (constructor selection via reflection access).
src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperGlobalTests.cs Adds Azure-present tests that force the full bootstrap and verify global provider registration.
src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs Moves these tests into the global serialized collection to account for shared registry mutations.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs Updates tests to validate the public API delegates to the shared registry instance (no SqlClient assembly required).
src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/AuthenticationProviderRegistryTest.cs Adds comprehensive isolated tests for registry behavior (overrides, permanence, lifecycle callbacks).
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.zh-Hant.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.zh-Hans.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.tr.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ru.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.resx Adds the invariant resource string(s) used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.pt-BR.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.pl.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ko.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.ja.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.it.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.fr.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.es.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs Adds the strongly-typed resource accessor for Abstractions strings.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.de.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.cs.resx Adds localized string resources used by Abstractions exception messages.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs Removes the Abstractions→SqlClient reflection bridge for provider get/set.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs Rewires GetProvider/SetProvider to call the shared registry directly.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/IsExternalInit.cs Adds an IsExternalInit polyfill for record/init support on non-NET TFMs.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AuthenticationProviderRegistry.cs Introduces the shared concurrent provider registry with permanence semantics and lifecycle callback handling.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj Updates InternalsVisibleTo to include SqlClient and UnitTests under signing conditions.
src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml Updates docs to reflect concurrency/idempotency expectations and clarifies canonical Get/Set usage.
aot-auth-provider-proposal.md Adds/updates design proposal documentation for the AOT-safe registration approach.
Files not reviewed (1)
  • src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file
Suppressed comments (2)

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AuthenticationBootstrapper.cs:280

  • These trace calls build interpolated/concatenated strings at the call site, which allocates even when tracing is disabled. Since SqlClientEventSource.TryTraceEvent only formats when enabled, switch to a parameterized call and compute expensive arguments (like the token string) only inside an IsTraceEnabled() guard.
    src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388
  • The remarks still refer to the old cached-field name (s_useManagedNetworking). After the rename to s_useManagedNetworkingOnWindows, this comment is now inaccurate and could mislead future edits to this reflection-based helper.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@paulmedynski paulmedynski added Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. Area\Azure Connectivity Use this to tag issues that are related to Azure connectivity. labels Aug 21, 2026
@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Aug 21, 2026
@paulmedynski paulmedynski added this to the 8.0.0-preview1 milestone Aug 21, 2026
@paulmedynski paulmedynski modified the milestones: 8.0.0-preview1, 7.1.0 Sep 1, 2026
Copilot AI review requested due to automatic review settings September 8, 2026 19:19
@paulmedynski
paulmedynski force-pushed the dev/paul/aot-azure-fix branch from 96c9759 to 78b8175 Compare September 8, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The AOT compatibility tool currently reports an incorrect effective default when the AppContext switch is absent, which can mislead validation runs and should be corrected before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Strings.Designer.cs: Generated file

Suppressed comments (1)

src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388

  • The XML doc comment still refers to the old private field name s_useManagedNetworking, but the implementation was renamed to s_useManagedNetworkingOnWindows. Also, the field is now compiled for all #if NET builds; it’s only the behavior that’s Windows-guarded, so the remark about the reflection lookup failing on non-Windows is no longer accurate.
  • Files reviewed: 54/55 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +29 to +31
bool switchFound = AppContext.TryGetSwitch(
"Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery",
out bool reflectionEnabled);
SqlAuthenticationMethod authenticationMethod)
{
return Internal.GetProvider(authenticationMethod);
return AuthenticationProviderRegistry.Instance.GetProvider(authenticationMethod);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Bootstrap before reading or writing the shared registry

GetProvider and SetProvider now access the initially empty registry without triggering discovery. If an application calls SetProvider before its first federated connection, AuthenticationBootstrapper.LoadAzureExtensionProvider later calls the overridable Registry.SetProvider path for every Entra method and replaces the application’s provider. Previously, entering SqlAuthenticationProviderManager.SetProvider initialized configuration and Azure defaults first, so the explicit provider remained the final override. The same change makes GetProvider return null before first authentication even when the Azure extension is available. Preserve the old initialization and precedence contract, either by bootstrapping before public registry access or by making default discovery add only missing providers.

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these files will be picked up by our localization flow, so any additions/changes won't receive updates. It only knows how to update the resx files at the existing location (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/Microsoft.Data.SqlClient?path=/Localize/LocProject.json). It looks like we can add an additional location? but it will take a bit of experimentation.

If you don't want to do that now, a cheap alternative without modifying the localization pipeline could be to copy the resource files over to this package at build time.

@github-project-automation github-project-automation Bot moved this from In progress to Waiting for customer in SqlClient Board Sep 9, 2026
/// <see cref="SetPermanentProvider"/>) and therefore not overridable by <see cref="SetProvider"/>.
/// </summary>
/// <param name="Provider">The registered provider. Never <see langword="null"/>.</param>
/// <param name="IsPermanent">Whether the provider must not be overridden by <see cref="SetProvider"/>.</param>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski "True if the provider ..."


/// <summary>
/// Initializes a new, empty registry. Production code uses the shared <see cref="Instance"/>;
/// the constructor is exposed to tests so they can exercise registry behavior in isolation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski "Instance. This constructor is exposed so tests can exercise ..."

/// <summary>
/// Registers an overridable provider for the given authentication method.
/// </summary>
/// <returns>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski Missing param docs.

/// (permanent or not). Only <see cref="SetProvider"/> is blocked by an existing permanent
/// provider; <see cref="SetPermanentProvider"/> itself always overwrites.
/// </para>
/// </remarks>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski Missing param docs.

// That is why this class lives in the [Collection("SqlAuthenticationProviderGlobal")] collection,
// which serializes it with the other tests that mutate the shared registry.
//
// TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/41888):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski I think this prerequisite work is done.

// from Microsoft.Data.SqlClient. This call has no global side effects -- it just returns a new
// provider instance.
//
// TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/41888):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski I think we have IVT for this now.

Assert.Throws<ArgumentNullException>(
() => new ActiveDirectoryAuthenticationProvider((ActiveDirectoryAuthenticationProviderOptions)null!));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This coverage was split across several other tests:

  • WAM-broker construction/configuration behavior is covered in:
    • src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperTests.cs
    • These verify that UseWamBroker = true is passed to the options constructor and preserved on the resulting ActiveDirectoryAuthenticationProvider.
  • Registration preserves the provider instance is covered in:
    • src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/AuthenticationProviderRegistryTest.cs
    • SetProvider() followed by GetProvider() asserts the same instance is returned.
    • SqlAuthenticationProviderTest.PublicApi_DelegatesToSharedInstance() also verifies the public API and registry observe the same instance.
  • Provider installation for all Active Directory methods is covered in:
    • src/Microsoft.Data.SqlClient.Extensions/Azure/test/AuthenticationBootstrapperGlobalTests.cs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski Move this beside our other design docs.


// CreateAzureAuthenticationProvider tests ----------------------------------------------
//
// Each Stub* container mimics one shape the real Azure extension might expose:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulmedynski Do we need this now that we release all SqlClient family packages together?

@paulmedynski
paulmedynski removed this pull request from stack #4571 September 10, 2026 12:45
@paulmedynski
paulmedynski changed the base branch from dev/paul/assembly-signing-azure to dev/paul/auth-registry-abstractions September 10, 2026 12:45
@paulmedynski
paulmedynski added this pull request to stack #4671 September 10, 2026 12:45
Even with the registry moved into Abstractions, the driver still discovers
app.config providers and the Azure extension via reflection (Type.GetType,
Assembly.Load, Activator.CreateInstance).  Under NativeAOT the trimmer has no
static reference to follow, so it removes the Azure extension and every Active
Directory authentication method silently loses its provider.  None of the
reflection carried trim annotations, so the compiler emitted no warnings.

Add a feature switch that lets the trimmer remove the reflection entirely:

- Add the EnableReflectionBasedAuthenticationProviderDiscovery app context
  switch (default true) and guard the bootstrapper's discovery with it.  On
  .NET 9+ it carries [FeatureSwitchDefinition]; ILLink.Substitutions.xml
  provides the same substitution on .NET 8.
- Annotate LoadConfiguration and LoadAzureExtensionProvider with
  [RequiresUnreferencedCode] and [RequiresDynamicCode] so trim analysis warns
  even when the switch is left enabled.

AOT applications set the switch to false at publish time via
RuntimeHostConfigurationOption with Trim="true".  The trimmer then folds the
guard to a constant false and eliminates the reflection code, and the app
registers its providers explicitly with SqlAuthenticationProvider.SetProvider.

Also split UseManagedNetworking into a platform guard plus a
UseManagedNetworkingOnWindows substitution target.  The old property mixed the
OS check with the switch read, so the existing substitution could only be
embedded conditionally; with the guard hoisted out, the substitution is safe on
every platform and ILLink.Substitutions.xml is now unconditional for .NET.

Add tools/AotCompatibility, a standalone publish harness that verifies the
substitution actually removes LoadAzureExtensionProvider from the native image.
Copilot AI review requested due to automatic review settings September 10, 2026 12:47
@paulmedynski paulmedynski changed the title AOT-safe authentication provider discovery with feature switch and trimmer support Make authentication provider discovery AOT-safe Sep 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are a few correctness/documentation mismatches in the new AOT verification harness and switch documentation that should be fixed to avoid misleading diagnostics and false-green validation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs:388

  • The remarks for UseManagedNetworking still refer to the old backing field name s_useManagedNetworking, but the implementation now uses s_useManagedNetworkingOnWindows. Leaving this stale reference makes the guidance misleading for future maintainers (and for anyone updating the reflection helper again).
    /// The underlying s_useManagedNetworking field only exists in the SqlClient
    /// assembly when it is built for .NET on Windows. The getter reads the
    /// public LocalAppContextSwitches.UseManagedNetworking property, which
    /// exists on all platforms and is safe to read anywhere. Only the setter
    /// relies on the s_useManagedNetworking field, so callers must set this

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs:695

  • The summary for EnableReflectionBasedAuthenticationProviderDiscovery mentions only Azure extension loading, but the switch is also used to guard config-based provider/initializer loading. The XML summary should reflect the full behavior to prevent confusion about what is disabled under AOT trimming.
    /// <summary>
    /// When set to true (the default), AuthenticationBootstrapper will
    /// use reflection (Assembly.Load + Activator.CreateInstance) to discover
    /// and load the Azure extension authentication provider at startup.
    ///
  • Files reviewed: 13/13 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment on lines +199 to +203
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(" WARN: Reflection code absent despite discovery being enabled.");
Console.ResetColor();
}
Comment on lines +91 to +95
_sqlAuthLogger.LogInfo(
nameof(AuthenticationBootstrapper),
"Ctor",
"Reflection-based provider discovery is disabled; skipping app.config " +
"authentication provider configuration.");
Comment on lines +159 to +163
/// <summary>
/// The name of the app context switch that controls whether
/// AuthenticationBootstrapper uses reflection to discover and load
/// the Azure extension authentication provider at startup.
/// </summary>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Azure Connectivity Use this to tag issues that are related to Azure connectivity. Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems.

Projects

Status: Waiting for customer

Development

Successfully merging this pull request may close these issues.

Entra ID authentication broken under NativeAOT in v7.0 — Extensions.Azure reflection-based discovery is AOT-incompatible

5 participants