Skip to content

Conversation

@mdaigle
Copy link
Contributor

@mdaigle mdaigle commented Jan 13, 2026

Description

Refactors code in ActiveDirectoryAuthenticationProvider to improve readability and modularity.
Adds a new prompt for doc comment generation and adds doc comments to ActiveDirectoryAuthenticationProvider.
The Integrated auth method is marked as obsolete by MSAL. This PR carries forward that obsolete designation and is a public API change.

A targeted PR for WAM support will build on top of these refactors.

Issues

#3418

Testing

No new logic, only refactors. Existing tests should provide coverage.

Copilot AI review requested due to automatic review settings January 13, 2026 20:48
@mdaigle mdaigle marked this pull request as ready for review January 13, 2026 20:48
@mdaigle mdaigle requested a review from a team as a code owner January 13, 2026 20:48
@mdaigle mdaigle changed the title [azure-split] Refactors to simplify WAM support diff [feat/azure-split] Refactors to simplify WAM support diff Jan 13, 2026
Copy link
Contributor

Copilot AI left a comment

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 refactors the ActiveDirectoryAuthenticationProvider class to improve code modularity and readability in preparation for WAM (Windows Authentication Manager) support. The main changes include extracting authentication logic into separate methods, adding comprehensive XML documentation comments, and introducing a new GitHub Copilot prompt for documentation generation.

Changes:

  • Refactored AcquireTokenAsync to use a switch statement and delegate to specialized methods for each authentication type
  • Extracted authentication flows into dedicated methods (AcquireTokenIntegratedAsync, AcquireTokenByUsernamePasswordAsync, AcquireTokenInteractiveAsync, AcquireTokenDeviceFlowAsync)
  • Added comprehensive XML documentation comments to all internal members and helper classes
  • Fixed a bug in the connection timeout overflow check logic
  • Added obsolete attribute to ActiveDirectoryIntegrated authentication method
  • Created a new prompt file for generating documentation comments

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.

File Description
src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs Major refactoring with extracted methods, added doc comments, and fixed timeout logic bug
src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationMethod.cs Added obsolete attribute to ActiveDirectoryIntegrated, corrected comment grammar
.github/prompts/doc-comments.prompt.md New GitHub Copilot prompt for generating XML documentation
Comments suppressed due to low confidence (1)

src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs:193

  • The condition was inverted from the original, which appears to fix a bug. The original code if (int.MaxValue / 1000 > parameters.ConnectionTimeout) would set CancelAfter to int.MaxValue when the timeout was SMALL (safe to multiply), which is backwards. The new code correctly checks if the timeout is too large to safely multiply by 1000 before capping it. This is a beneficial bug fix, but it represents a behavior change that should be noted in testing.
                if (parameters.ConnectionTimeout > int.MaxValue / 1000)
                {
                    cts.CancelAfter(int.MaxValue);
                }
                else
                {
                    cts.CancelAfter(parameters.ConnectionTimeout * 1000);
                }

/// </summary>
/// <param name="publicClientAppKey">The key containing configuration for the application.</param>
/// <returns>A new <see cref="IPublicClientApplication"/> instance.</returns>
private static IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The CreateClientAppInstance method was changed from an instance method to a static method, but it attempts to reference _iWin32WindowFunc (an instance field) within the NETFRAMEWORK conditional block. This will cause a compilation error on NETFRAMEWORK builds. The method should either remain an instance method, or _iWin32WindowFunc should be passed as a parameter (e.g., via the PublicClientAppKey).

Suggested change
private static IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey)
private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey)

Copilot uses AI. Check for mistakes.
default:
{
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);

Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Remove trailing whitespace on this line.

Suggested change

Copilot uses AI. Check for mistakes.
.ExecuteAsync(ctsInteractive.Token)
.ConfigureAwait(false);
}
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

There is an extra space before the closing parenthesis in the format string. This appears in the tracing output and should be removed for consistency with other log messages.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult.ExpiresOn);

Copilot uses AI. Check for mistakes.
#endif
PublicClientAppKey pcaKey =
#if NETFRAMEWORK
new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The GetPublicClientAppInstanceAsync method was changed from an instance method to a static method, but it constructs a PublicClientAppKey that references _iWin32WindowFunc, which is an instance field only available on NETFRAMEWORK builds. This will cause a compilation error. The method should either remain an instance method, or _iWin32WindowFunc should be passed as a parameter from the calling context.

Suggested change
new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc);
new(parameters.Authority, redirectUri, applicationClientId);

Copilot uses AI. Check for mistakes.
Comment on lines +707 to +709
AuthenticationResult result = await app.AcquireTokenInteractive(scopes)
.WithCorrelationId(connectionId)
.WithCustomWebUi(customWebUI)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The refactored code now unconditionally calls WithCustomWebUi(customWebUI) even when customWebUI is null, whereas the original code had separate branches that only called WithCustomWebUi when customWebUI was not null. If the MSAL library does not handle null values gracefully for this method, this could cause a runtime error. Consider adding a conditional check: if customWebUI is not null, call WithCustomWebUi(customWebUI) on the builder before calling WithLoginHint.

Suggested change
AuthenticationResult result = await app.AcquireTokenInteractive(scopes)
.WithCorrelationId(connectionId)
.WithCustomWebUi(customWebUI)
var interactiveBuilder = app.AcquireTokenInteractive(scopes)
.WithCorrelationId(connectionId);
if (customWebUI is not null)
{
interactiveBuilder = interactiveBuilder.WithCustomWebUi(customWebUI);
}
AuthenticationResult result = await interactiveBuilder

Copilot uses AI. Check for mistakes.
.WithCorrelationId(connectionId)
.ExecuteAsync(cancellationToken: cts.Token)
.ConfigureAwait(false);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

There is an extra space before the closing parenthesis in the format string. This appears in the tracing output and should be removed for consistency with other log messages.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult.ExpiresOn);

Copilot uses AI. Check for mistakes.
AuthenticationResult result = await builder
.ExecuteAsync(cancellationToken: cts.Token)
.ConfigureAwait(false);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The log message says "Acquired access token (interactive)" but this should say "Acquired access token for Active Directory Integrated auth mode" to match the original log message format for this authentication method.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn);

Copilot uses AI. Check for mistakes.

try
{
CancellationTokenSource ctsInteractive = new();
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Disposable 'CancellationTokenSource' is created but not disposed.

Suggested change
CancellationTokenSource ctsInteractive = new();
using CancellationTokenSource ctsInteractive = new();

Copilot uses AI. Check for mistakes.
}

#pragma warning disable CS0618 // Type or member is obsolete
AuthenticationResult result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Call to obsolete method AcquireTokenByUsernamePassword.

Copilot uses AI. Check for mistakes.
// to acquire tokens for Active Directory Integrated
// authentication.
#pragma warning disable CS0618 // Type or member is obsolete
var builder = app.AcquireTokenByIntegratedWindowsAuth(scopes)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Call to obsolete method AcquireTokenByIntegratedWindowsAuth.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 13, 2026 22:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

{
// Safely convert to milliseconds.
if (int.MaxValue / 1000 > parameters.ConnectionTimeout)
if (parameters.ConnectionTimeout > int.MaxValue / 1000)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The timeout comparison logic is inverted. This condition checks if the timeout is greater than the safe conversion threshold, but then applies int.MaxValue as the timeout. The original logic int.MaxValue / 1000 > parameters.ConnectionTimeout was correct: it checked if the value was safe to convert, then converted it. The inverted logic will now apply int.MaxValue when the timeout is too large, and attempt to convert potentially unsafe values when the timeout is within range.

Copilot uses AI. Check for mistakes.
default:
{
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);

Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Remove the trailing whitespace on this empty line for consistency with the codebase's formatting standards.

Suggested change

Copilot uses AI. Check for mistakes.
.ExecuteAsync(ctsInteractive.Token)
.ConfigureAwait(false);
}
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Remove the extra space before .ExpiresOn to maintain consistent spacing in member access.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult.ExpiresOn);

Copilot uses AI. Check for mistakes.
.WithCorrelationId(connectionId)
.ExecuteAsync(cancellationToken: cts.Token)
.ConfigureAwait(false);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

Remove the extra space before .ExpiresOn to maintain consistent spacing in member access.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn);
SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult.ExpiresOn);

Copilot uses AI. Check for mistakes.
#endif
PublicClientAppKey pcaKey =
#if NETFRAMEWORK
new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

This line references _iWin32WindowFunc which is an instance field, but it's being accessed in a static method. The method GetPublicClientAppInstanceAsync is now static (line 778), so it cannot access the instance field _iWin32WindowFunc. This will cause a compilation error.

Suggested change
new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc);
new(parameters.Authority, redirectUri, applicationClientId, null);

Copilot uses AI. Check for mistakes.
Comment on lines +1082 to 1087
return string.CompareOrdinal(_authority, pcaKey._authority) == 0
&& string.CompareOrdinal(_redirectUri, pcaKey._redirectUri) == 0
&& string.CompareOrdinal(_applicationClientId, pcaKey._applicationClientId) == 0
#if NETFRAMEWORK
&& pcaKey._iWin32WindowFunc == _iWin32WindowFunc
#endif
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The standalone semicolon on line 1088 is unnecessary and reduces readability. Place the semicolon at the end of the last condition on line 1087 (or line 1084 for non-NETFRAMEWORK builds) instead.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants