[feat/azure-split] Refactors to simplify WAM support diff#3888
[feat/azure-split] Refactors to simplify WAM support diff#3888mdaigle wants to merge 20 commits intofeat/azure-splitfrom
Conversation
…AuthenticationProvider.cs
There was a problem hiding this comment.
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
AcquireTokenAsyncto 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) |
There was a problem hiding this comment.
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).
| private static IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) | |
| private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) |
| default: | ||
| { | ||
| SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); | ||
|
|
There was a problem hiding this comment.
Remove trailing whitespace on this line.
| .ExecuteAsync(ctsInteractive.Token) | ||
| .ConfigureAwait(false); | ||
| } | ||
| SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn); |
There was a problem hiding this comment.
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.
| 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); |
| #endif | ||
| PublicClientAppKey pcaKey = | ||
| #if NETFRAMEWORK | ||
| new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc); |
There was a problem hiding this comment.
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.
| new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc); | |
| new(parameters.Authority, redirectUri, applicationClientId); |
| AuthenticationResult result = await app.AcquireTokenInteractive(scopes) | ||
| .WithCorrelationId(connectionId) | ||
| .WithCustomWebUi(customWebUI) |
There was a problem hiding this comment.
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.
| 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 |
| .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); |
There was a problem hiding this comment.
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.
| 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); |
| 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); |
There was a problem hiding this comment.
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.
| 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); |
|
|
||
| try | ||
| { | ||
| CancellationTokenSource ctsInteractive = new(); |
There was a problem hiding this comment.
Disposable 'CancellationTokenSource' is created but not disposed.
| CancellationTokenSource ctsInteractive = new(); | |
| using CancellationTokenSource ctsInteractive = new(); |
| } | ||
|
|
||
| #pragma warning disable CS0618 // Type or member is obsolete | ||
| AuthenticationResult result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) |
There was a problem hiding this comment.
Call to obsolete method AcquireTokenByUsernamePassword.
| // to acquire tokens for Active Directory Integrated | ||
| // authentication. | ||
| #pragma warning disable CS0618 // Type or member is obsolete | ||
| var builder = app.AcquireTokenByIntegratedWindowsAuth(scopes) |
There was a problem hiding this comment.
Call to obsolete method AcquireTokenByIntegratedWindowsAuth.
| { | ||
| // Safely convert to milliseconds. | ||
| if (int.MaxValue / 1000 > parameters.ConnectionTimeout) | ||
| if (parameters.ConnectionTimeout > int.MaxValue / 1000) |
There was a problem hiding this comment.
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.
| default: | ||
| { | ||
| SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); | ||
|
|
There was a problem hiding this comment.
Remove the trailing whitespace on this empty line for consistency with the codebase's formatting standards.
| .ExecuteAsync(ctsInteractive.Token) | ||
| .ConfigureAwait(false); | ||
| } | ||
| SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, cachedResult .ExpiresOn); |
There was a problem hiding this comment.
Remove the extra space before .ExpiresOn to maintain consistent spacing in member access.
| 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); |
| .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); |
There was a problem hiding this comment.
Remove the extra space before .ExpiresOn to maintain consistent spacing in member access.
| 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); |
| #endif | ||
| PublicClientAppKey pcaKey = | ||
| #if NETFRAMEWORK | ||
| new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc); |
There was a problem hiding this comment.
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.
| new(parameters.Authority, redirectUri, applicationClientId, _iWin32WindowFunc); | |
| new(parameters.Authority, redirectUri, applicationClientId, null); |
| 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 |
There was a problem hiding this comment.
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.
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.