/// <summary> /// Get token. /// </summary> public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(() => { IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .Build(); AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; // Note: CorrelationId, which existed in ADAL, can not be set in MSAL (yet?). // parameter.ConnectionId was passed as the CorrelationId in ADAL to aid support in troubleshooting. // If/When MSAL adds CorrelationId support, it should be passed from parameters here, too. SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password).ExecuteAsync().Result; return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); });
/// <summary> /// Get token. /// </summary> public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() => { IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .Build(); AuthenticationResult result; string[] scopes = parameters.Scopes; // Note: CorrelationId, which existed in ADAL, can not be set in MSAL (yet?). // parameter.ConnectionId was passed as the CorrelationId in ADAL to aid support in troubleshooting. // If/When MSAL adds CorrelationId support, it should be passed from parameters here, too. if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { result = app.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync().Result; } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) { SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password).ExecuteAsync().Result; } else { result = await app.AcquireTokenInteractive(scopes) .WithUseEmbeddedWebView(true) .ExecuteAsync(); } return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); });
/// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml' path='docs/members[@name="SqlAuthenticationProvider"]/AcquireTokenAsync/*'/> public abstract Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters);
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/> public override async Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) { CancellationTokenSource cts = new CancellationTokenSource(); // Use Connection timeout value to cancel token acquire request after certain period of time. cts.CancelAfter(parameters.ConnectionTimeout * 1000); // Convert to milliseconds string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; int seperatorIndex = parameters.Authority.LastIndexOf('/'); string tenantId = parameters.Authority.Substring(seperatorIndex + 1); string authority = parameters.Authority.Remove(seperatorIndex + 1); TokenRequestContext tokenRequestContext = new TokenRequestContext(scopes); string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault) { DefaultAzureCredentialOptions defaultAzureCredentialOptions = new DefaultAzureCredentialOptions() { AuthorityHost = new Uri(authority), ManagedIdentityClientId = clientId, InteractiveBrowserTenantId = tenantId, SharedTokenCacheTenantId = tenantId, SharedTokenCacheUsername = clientId, VisualStudioCodeTenantId = tenantId, VisualStudioTenantId = tenantId, ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications. }; AccessToken accessToken = await new DefaultAzureCredential(defaultAzureCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn)); } TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions() { AuthorityHost = new Uri(authority) }; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryMSI) { AccessToken accessToken = await new ManagedIdentityCredential(clientId, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Managed Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn)); } AuthenticationResult result; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) { AccessToken accessToken = await new ClientSecretCredential(tenantId, parameters.UserId, parameters.Password, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", accessToken.ExpiresOn); return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn)); } /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. * * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris */ string redirectUri = s_nativeClientRedirectUri; #if NETCOREAPP if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { redirectUri = "http://localhost"; } #endif PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId #if NETFRAMEWORK , _iWin32WindowFunc #endif #if NETSTANDARD , _parentActivityOrWindowFunc #endif ); IPublicClientApplication app = GetPublicClientAppInstance(pcaKey); if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (!string.IsNullOrEmpty(parameters.UserId)) { result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .WithUsername(parameters.UserId) .ExecuteAsync(cancellationToken: cts.Token); } else { result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync(cancellationToken: cts.Token); } SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) { SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync(cancellationToken: cts.Token); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { // Fetch available accounts from 'app' instance System.Collections.Generic.IEnumerator <IAccount> accounts = (await app.GetAccountsAsync()).GetEnumerator(); IAccount account = default; if (accounts.MoveNext()) { if (!string.IsNullOrEmpty(parameters.UserId)) { do { IAccount currentVal = accounts.Current; if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0) { account = currentVal; break; } }while (accounts.MoveNext()); } else { account = accounts.Current; } } if (null != account) { try { // If 'account' is available in 'app', we use the same to acquire token silently. // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); } catch (MsalUiRequiredException) { // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), // or the user needs to perform two factor authentication. result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); } } else { // If no existing 'account' is found, we request user to sign in interactively. result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); } } else { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); } return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); }
/// <summary> /// Get token. /// </summary> public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() => { AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) { IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId) .WithAuthority(parameters.Authority) .WithClientSecret(parameters.Password) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .Build(); result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result; return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); } IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) #if netcoreapp .WithRedirectUri("http://localhost") #else /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. * * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris */ .WithRedirectUri("https://login.microsoftonline.com/oauth2/nativeclient") #endif .Build(); if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (!string.IsNullOrEmpty(parameters.UserId)) { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .WithUsername(parameters.UserId) .ExecuteAsync().Result; } else { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; } } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) { SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password) .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 { throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); } return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); });
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/> public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() => { AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) { IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId) .WithAuthority(parameters.Authority) .WithClientSecret(parameters.Password) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .Build(); result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", result.ExpiresOn); return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); } /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. * * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris */ string redirectUri = s_nativeClientRedirectUri; #if NETCOREAPP if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { redirectUri = "http://localhost"; } #endif PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId #if NETFRAMEWORK , _iWin32WindowFunc #endif #if NETSTANDARD , _parentActivityOrWindowFunc #endif ); IPublicClientApplication app = GetPublicClientAppInstance(pcaKey); if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (!string.IsNullOrEmpty(parameters.UserId)) { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .WithUsername(parameters.UserId) .ExecuteAsync().Result; } else { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; } SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) { SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { // Fetch available accounts from 'app' instance System.Collections.Generic.IEnumerable <IAccount> 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 { // If 'account' is available in 'app', we use the same to acquire token silently. // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } catch (MsalUiRequiredException) { // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), // or the user needs to perform two factor authentication. result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } } else { // If no existing 'account' is found, we request user to sign in interactively. result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } } else { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); } return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); });
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/> public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() => { AuthenticationResult result; string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; string[] scopes = new string[] { scope }; if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) { IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId) .WithAuthority(parameters.Authority) .WithClientSecret(parameters.Password) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .Build(); result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", result.ExpiresOn); return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); } /* * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. * * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris */ string redirectURI = "https://login.microsoftonline.com/common/oauth2/nativeclient"; #if NETCOREAPP if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { redirectURI = "http://localhost"; } #endif IPublicClientApplication app; #if NETSTANDARD if (parentActivityOrWindowFunc != null) { app = PublicClientApplicationBuilder.Create(_applicationClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .WithRedirectUri(redirectURI) .WithParentActivityOrWindow(parentActivityOrWindowFunc) .Build(); } #endif #if NETFRAMEWORK if (_iWin32WindowFunc != null) { app = PublicClientApplicationBuilder.Create(_applicationClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .WithRedirectUri(redirectURI) .WithParentActivityOrWindow(_iWin32WindowFunc) .Build(); } #endif #if !NETCOREAPP else #endif { app = PublicClientApplicationBuilder.Create(_applicationClientId) .WithAuthority(parameters.Authority) .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) .WithRedirectUri(redirectURI) .Build(); } if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (!string.IsNullOrEmpty(parameters.UserId)) { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .WithUsername(parameters.UserId) .ExecuteAsync().Result; } else { result = app.AcquireTokenByIntegratedWindowsAuth(scopes) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; } SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) { SecureString password = new SecureString(); foreach (char c in parameters.Password) { password.AppendChar(c); } password.MakeReadOnly(); result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password) .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync().Result; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result.ExpiresOn); } else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { System.Collections.Generic.IEnumerable <IAccount> 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(); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } catch (MsalUiRequiredException) { result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } } else { result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn); } } else { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); } return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn)); });
// Reference: https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http public override async Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) { HttpClient httpClient = s_httpClient; try { // Check if App Services MSI is available. If both these environment variables are set, then it is. string msiEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT"); string msiHeader = Environment.GetEnvironmentVariable("IDENTITY_HEADER"); var isAppServicesMsiAvailable = !string.IsNullOrWhiteSpace(msiEndpoint) && !string.IsNullOrWhiteSpace(msiHeader); // if App Service MSI is not available then Azure VM IMDS may be available, test with a probe request if (!isAppServicesMsiAvailable) { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | This environment is not identified as an Azure App Service environment. Proceeding to validate Azure VM IMDS endpoint availability."); using (var internalTokenSource = new CancellationTokenSource()) using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(internalTokenSource.Token, default)) { HttpRequestMessage imdsProbeRequest = new HttpRequestMessage(HttpMethod.Get, AzureVmImdsEndpoint); try { internalTokenSource.CancelAfter(_azureVmImdsProbeTimeout); await httpClient.SendAsync(imdsProbeRequest, linkedTokenSource.Token).ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | The Instance Metadata Service (IMDS) service endpoint is accessible. Proceeding to acquire access token."); } catch (OperationCanceledException) { // request to IMDS timed out (internal cancellation token canceled), neither Azure VM IMDS nor App Services MSI are available if (internalTokenSource.Token.IsCancellationRequested) { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | The Instance Metadata Service (IMDS) service endpoint is not accessible."); // Throw error: Tried to get token using Managed Identity. Unable to connect to the Instance Metadata Service (IMDS). Skipping request to the Managed Service Identity (MSI) token endpoint. throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_MetadataEndpointNotListening}"); } throw; } } } else { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | This environment is identified as an Azure App Service environment. Proceeding to acquire access token from Endpoint URL: {0}", msiEndpoint); } string objectIdParameter = string.Empty; // If user assigned managed identity is specified, include object ID parameter in request if (parameters.UserId != default) { objectIdParameter = $"&object_id={Uri.EscapeDataString(parameters.UserId)}"; SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Identity Object id received and will be used for acquiring access token {0}", parameters.UserId); } // Craft request as per the MSI protocol var requestUrl = isAppServicesMsiAvailable ? $"{msiEndpoint}?resource={parameters.Resource}{objectIdParameter}{AzureSystemApiVersion}" : $"{AzureVmImdsEndpoint}?resource={parameters.Resource}{objectIdParameter}{AzureVmImdsApiVersion}"; HttpResponseMessage response = null; try { response = await httpClient.SendAsyncWithRetry(getRequestMessage, _retryTimeoutInSeconds, _maxRetryCount, default).ConfigureAwait(false); HttpRequestMessage getRequestMessage() { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); if (isAppServicesMsiAvailable) { request.Headers.Add("X-IDENTITY-HEADER", msiHeader); } else { request.Headers.Add("Metadata", "true"); } return(request); } } catch (HttpRequestException) { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Failed after 5 retries. Unable to connect to the Managed Service Identity (MSI) endpoint."); // Throw error: Tried to get token using Managed Service Identity. Failed after 5 retries. Unable to connect to the Managed Service Identity (MSI) endpoint. Please check that you are running on an Azure resource that has MSI setup. throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_RetryFailure} {Strings.Azure_IdentityEndpointNotListening}"); } // If the response is successful, it should have JSON response with an access_token field if (response.IsSuccessStatusCode) { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Successful response received. Status Code {0}", response.StatusCode); string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); int accessTokenStartIndex = jsonResponse.IndexOf(AccessToken) + AccessToken.Length + 3; var imdsAccessToken = jsonResponse.Substring(accessTokenStartIndex, jsonResponse.IndexOf('"', accessTokenStartIndex) - accessTokenStartIndex); var expiresin = jsonResponse.Substring(jsonResponse.IndexOf(Expiry) + Expiry.Length + 3, FileTimeLength); DateTime expiryTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(long.Parse(expiresin)); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Access Token received. Expiry Time: {0}", expiryTime); return(new SqlAuthenticationToken(imdsAccessToken, expiryTime)); } // RetryFailure : Failed after 5 retries. // NonRetryableError : Received a non-retryable error. SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Request to acquire access token failed with status code {0}", response.StatusCode); string errorStatusDetail = response.IsRetryableStatusCode() ? Strings.Azure_RetryFailure : Strings.Azure_NonRetryableError; string errorText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Error occurred while acquiring access token: {0} Identity Response Code: {1}, Response: {2}", errorStatusDetail, response.StatusCode, errorText); throw SQL.Azure_ManagedIdentityException($"{errorStatusDetail} Identity Response Code: {response.StatusCode}, Response: {errorText}"); } catch (Exception exp) { SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Error occurred while acquiring access token: {0}", exp.Message); if (exp is SqlException) { throw; } // Throw error: Access token could not be acquired. {exp.Message} throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_GenericErrorMessage} {exp.Message}"); } }