diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
index 182e9e5fe8..d9497a9bce 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
@@ -106,7 +106,7 @@ internal static string ConvertToString(object value)
internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result)
{
- Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 5, "SqlAuthenticationMethod enum has changed, update needed");
+ Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed");
bool isSuccess = false;
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index 8bc0eb8d46..303f14596c 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
@@ -2242,7 +2242,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
}
break;
default:
- throw new InvalidOperationException($"Failed to get a token with unsupported auth method {ConnectionOptions.Authentication}.");
+ throw SQL.UnsupportedAuthenticationSpecified(ConnectionOptions.Authentication);
}
Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null.");
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 191b7039f8..84b1e47fa5 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -390,6 +390,11 @@ internal static Exception UnsupportedSqlAuthenticationMethod(SqlAuthenticationMe
return ADP.NotSupported(System.SRHelper.GetString(SR.SQL_UnsupportedSqlAuthenticationMethod, authentication));
}
+ internal static Exception UnsupportedAuthenticationSpecified(SqlAuthenticationMethod authentication)
+ {
+ return ADP.InvalidOperation(System.SRHelper.GetString(SR.SQL_UnsupportedAuthenticationSpecified, authentication));
+ }
+
internal static Exception CannotCreateAuthProvider(string authentication, string type, Exception e)
{
return ADP.Argument(System.SRHelper.GetString(SR.SQL_CannotCreateAuthProvider, authentication, type), e);
@@ -425,6 +430,12 @@ internal static Exception ParameterCannotBeEmpty(string paramName)
return ADP.ArgumentNull(System.SRHelper.GetString(SR.SQL_ParameterCannotBeEmpty, paramName));
}
+ internal static Exception ActiveDirectoryInteractiveTimeout()
+ {
+ return ADP.TimeoutException(SR.SQL_Timeout_Active_Directory_Interactive_Authentication);
+ }
+
+
//
// SQL.DataCommand
//
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
index c3e62e211d..056cb98dbd 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.Designer.cs
@@ -3147,6 +3147,15 @@ internal static string SQL_Timeout {
}
}
+ ///
+ /// Looks up a localized string similar to Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request..
+ ///
+ internal static string SQL_Timeout_Active_Directory_Interactive_Authentication {
+ get {
+ return ResourceManager.GetString("SQL_Timeout_Active_Directory_Interactive_Authentication", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding..
///
@@ -3300,6 +3309,15 @@ internal static string SQL_UnsupportedAuthenticationByProvider {
}
}
+ ///
+ /// Looks up a localized string similar to Unsupported authentication specified in this context: {0}.
+ ///
+ internal static string SQL_UnsupportedAuthenticationSpecified {
+ get {
+ return ResourceManager.GetString("SQL_UnsupportedAuthenticationSpecified", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The server is attempting to use a feature that is not supported on this platform..
///
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
index f4168cd021..930d7ad451 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/SR.resx
@@ -1869,4 +1869,10 @@
Cannot use 'Authentication=Active Directory Integrated', if the Credential property has been set.
-
\ No newline at end of file
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
+
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
index 379014c97a..b0ba92425d 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
@@ -525,7 +525,7 @@ internal static ApplicationIntent ConvertToApplicationIntent(string keyword, obj
internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result)
{
- Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 5, "SqlAuthenticationMethod enum has changed, update needed");
+ Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 6, "SqlAuthenticationMethod enum has changed, update needed");
bool isSuccess = false;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index 0443daec52..3024273c1c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
@@ -2723,7 +2723,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
}
break;
default:
- throw new InvalidOperationException($"Failed to get a token with unsupported auth method {ConnectionOptions.Authentication}.");
+ throw SQL.UnsupportedAuthenticationSpecified(ConnectionOptions.Authentication);
}
Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null.");
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
index 55b1141f5c..45ed5cd2e3 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -421,6 +421,11 @@ static internal Exception UnsupportedSqlAuthenticationMethod(SqlAuthenticationMe
return ADP.NotSupported(StringsHelper.GetString(Strings.SQL_UnsupportedSqlAuthenticationMethod, authentication));
}
+ static internal Exception UnsupportedAuthenticationSpecified(SqlAuthenticationMethod authentication)
+ {
+ return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_UnsupportedAuthenticationSpecified, authentication));
+ }
+
static internal Exception CannotCreateAuthProvider(string authentication, string type, Exception e)
{
return ADP.Argument(StringsHelper.GetString(Strings.SQL_CannotCreateAuthProvider, authentication, type), e);
@@ -456,6 +461,11 @@ static internal Exception ParameterCannotBeEmpty(string paramName)
return ADP.ArgumentNull(StringsHelper.GetString(Strings.SQL_ParameterCannotBeEmpty, paramName));
}
+ static internal Exception ActiveDirectoryInteractiveTimeout()
+ {
+ return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_Interactive_Authentication);
+ }
+
//
// SQL.DataCommand
//
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index 0e5f67f488..40921911b0 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -10071,6 +10071,15 @@ internal static string SQL_Timeout {
}
}
+ ///
+ /// Looks up a localized string similar to Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request..
+ ///
+ internal static string SQL_Timeout_Active_Directory_Interactive_Authentication {
+ get {
+ return ResourceManager.GetString("SQL_Timeout_Active_Directory_Interactive_Authentication", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding..
///
@@ -10242,6 +10251,15 @@ internal static string SQL_UnsupportedAuthenticationByProvider {
}
}
+ ///
+ /// Looks up a localized string similar to Unsupported authentication specified in this context: {0}.
+ ///
+ internal static string SQL_UnsupportedAuthenticationSpecified {
+ get {
+ return ResourceManager.GetString("SQL_UnsupportedAuthenticationSpecified", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to SQL authentication method '{0}' is not supported..
///
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index d6f8ec6e0b..8a0176ed0d 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4530,4 +4530,10 @@
UDT size must be less than {1}, size: {0}
+
+ Unsupported authentication specified in this context: {0}
+
+
+ Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request.
+
\ No newline at end of file
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
index c80f7aa143..d533fbee58 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs
@@ -2,7 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Diagnostics;
+using System.Linq;
using System.Security;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
@@ -44,6 +48,7 @@ public override Task AcquireTokenAsync(SqlAuthentication
.WithAuthority(parameters.Authority)
.WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
.WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
+ .WithRedirectUri("http://localhost")
.Build();
if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
@@ -62,17 +67,89 @@ public override Task AcquireTokenAsync(SqlAuthentication
.WithCorrelationId(parameters.ConnectionId)
.ExecuteAsync().Result;
}
+ else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive)
+ {
+ var accounts = await app.GetAccountsAsync();
+ IAccount account;
+ if (!string.IsNullOrEmpty(parameters.UserId))
+ {
+ account = accounts.FirstOrDefault(a => parameters.UserId.Equals(a.Username, System.StringComparison.InvariantCultureIgnoreCase));
+ }
+ else
+ {
+ account = accounts.FirstOrDefault();
+ }
+
+ if (null != account)
+ {
+ try
+ {
+ result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
+ }
+ catch (MsalUiRequiredException)
+ {
+ result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
+ }
+ }
+ else
+ {
+ result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
+ }
+ }
else
{
- result = await app.AcquireTokenInteractive(scopes)
- .WithCorrelationId(parameters.ConnectionId)
- .WithLoginHint(parameters.UserId)
- .ExecuteAsync();
+ throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
}
return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
});
+ private async Task AcquireTokenInteractive(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId)
+ {
+ CancellationTokenSource cts = new CancellationTokenSource();
+#if netcoreapp
+ /*
+ * On .NET Core, MSAL will start the system browser as a separate process. MSAL does not have control over this browser,
+ * but once the user finishes authentication, the web page is redirected in such a way that MSAL can intercept the Uri.
+ * MSAL cannot detect if the user navigates away or simply closes the browser. Apps using this technique are encouraged
+ * to define a timeout (via CancellationToken). We recommend a timeout of at least a few minutes, to take into account
+ * cases where the user is prompted to change password or perform 2FA.
+ *
+ * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core#system-browser-experience
+ */
+ cts.CancelAfter(180000);
+#endif
+ try
+ {
+ return await app.AcquireTokenInteractive(scopes)
+ /*
+ * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table:
+ *
+ * Framework Embedded System Default
+ * -------------------------------------------
+ * .NET Classic Yes Yes^ Embedded
+ * .NET Core No Yes^ System
+ * .NET Standard No No NONE
+ * UWP Yes No Embedded
+ * Xamarin.Android Yes Yes System
+ * Xamarin.iOS Yes Yes System
+ * Xamarin.Mac Yes No Embedded
+ *
+ * ^ Requires "http://localhost" redirect URI
+ *
+ * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance
+ */
+ //.WithUseEmbeddedWebView(true)
+ .WithCorrelationId(connectionId)
+ .WithLoginHint(userId)
+ .ExecuteAsync(cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ throw SQL.ActiveDirectoryInteractiveTimeout();
+ }
+ }
+
///
/// Checks support for authentication type in lower case.
/// Interactive authentication added.