/// <summary> /// Used to get authentication result for Integrated Windows Authentication scenario, where the token may already be in ADAL cache. /// </summary> /// <param name="authority"></param> /// <param name="resource"></param> /// <param name="clientId"></param> /// <returns></returns> public async Task <AppAuthenticationResult> AcquireTokenSilentAsync(string authority, string resource, string clientId) { AuthenticationContext authenticationContext = new AuthenticationContext(authority); var authResult = await authenticationContext.AcquireTokenSilentAsync(resource, clientId).ConfigureAwait(false); return(AppAuthenticationResult.Create(authResult)); }
public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority, CancellationToken cancellationToken = default(CancellationToken)) { try { // Validate resource, since it gets sent as a command line argument to Azure CLI ValidationHelper.ValidateResource(resource); // Execute Azure CLI to get token string response = await _processManager.ExecuteAsync(new Process { StartInfo = GetProcessStartInfo(resource) }, cancellationToken).ConfigureAwait(false); // Parse the response TokenResponse tokenResponse = TokenResponse.Parse(response); var accessToken = tokenResponse.AccessToken2; AccessToken token = AccessToken.Parse(accessToken); PrincipalUsed.IsAuthenticated = true; if (token != null) { // Set principal used based on the claims in the access token. PrincipalUsed.UserPrincipalName = !string.IsNullOrEmpty(token.Upn) ? token.Upn : token.Email; PrincipalUsed.TenantId = token.TenantId; } return(AppAuthenticationResult.Create(tokenResponse)); } catch (Exception exp) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.AzureCliUsed} {AzureServiceTokenProviderException.GenericErrorMessage} {exp.Message}"); } }
/// <summary> /// Used to get authentication for client credentials flow using a client certificate. /// </summary> /// <param name="authority"></param> /// <param name="resource"></param> /// <param name="clientCertificate"></param> /// <returns></returns> public async Task <AppAuthenticationResult> AcquireTokenAsync(string authority, string resource, IClientAssertionCertificate clientCertificate) { var authenticationContext = GetAuthenticationContext(authority); var authResult = await authenticationContext.AcquireTokenAsync(resource, clientCertificate, true).ConfigureAwait(false); return(AppAuthenticationResult.Create(authResult)); }
/// <summary> /// Used to get authentication result for Integrated Windows Authentication scenario. /// </summary> /// <param name="authority"></param> /// <param name="resource"></param> /// <param name="clientId"></param> /// <param name="userCredential"></param> /// <returns></returns> public async Task <AppAuthenticationResult> AcquireTokenAsync(string authority, string resource, string clientId, UserCredential userCredential) { var authenticationContext = GetAuthenticationContext(authority); var authResult = await authenticationContext.AcquireTokenAsync(resource, clientId, userCredential).ConfigureAwait(false); return(AppAuthenticationResult.Create(authResult)); }
internal static AppAuthenticationResult Create(TokenResponse response, CultureInfo datetimeCulture = null) { if (response == null) { throw new ArgumentNullException(nameof(response)); } string expiresOnString = response.ExpiresOn ?? response.ExpiresOn2; DateTimeOffset expiresOn = DateTimeOffset.MinValue; double seconds; if (double.TryParse(expiresOnString, out seconds)) { expiresOn = AppAuthentication.AccessToken.UnixTimeEpoch.AddSeconds(seconds); } else if (!DateTimeOffset.TryParse(expiresOnString, datetimeCulture, DateTimeStyles.None, out expiresOn)) { throw new ArgumentException("ExpiresOn in token response could not be parsed"); } var result = new AppAuthenticationResult() { AccessToken = response.AccessToken ?? response.AccessToken2, ExpiresOn = expiresOn, Resource = response.Resource, TokenType = response.TokenType ?? response.TokenType2 }; return(result); }
internal static AppAuthenticationResult Create(TokenResponse response, TokenResponse.DateFormat dateFormat) { if (response == null) { throw new ArgumentNullException(nameof(response)); } string expiresOnString = response.ExpiresOn ?? response.ExpiresOn2; DateTimeOffset expiresOn = DateTimeOffset.MinValue; switch (dateFormat) { case TokenResponse.DateFormat.DateTimeString: expiresOn = DateTimeOffset.Parse(expiresOnString); break; case TokenResponse.DateFormat.Unix: double seconds = double.Parse(expiresOnString); expiresOn = AppAuthentication.AccessToken.UnixTimeEpoch.AddSeconds(seconds); break; } var result = new AppAuthenticationResult() { AccessToken = response.AccessToken ?? response.AccessToken2, ExpiresOn = expiresOn, Resource = response.Resource, TokenType = response.TokenType ?? response.TokenType2 }; return(result); }
internal static AppAuthenticationResult Create(AuthenticationResult authResult) { if (authResult == null) { throw new ArgumentNullException(nameof(authResult)); } var result = new AppAuthenticationResult() { AccessToken = authResult.AccessToken, ExpiresOn = authResult.ExpiresOn }; return(result); }
// For unit testing internal static AppAuthenticationResult Create(string accessToken) { if (string.IsNullOrEmpty(accessToken)) { throw new ArgumentNullException(nameof(accessToken)); } var tokenObj = AppAuthentication.AccessToken.Parse(accessToken); var result = new AppAuthenticationResult { AccessToken = accessToken, ExpiresOn = AppAuthentication.AccessToken.UnixTimeEpoch.AddSeconds(tokenObj.ExpiryTime) }; return(result); }
public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority, CancellationToken cancellationToken = default(CancellationToken)) { // Use the httpClient specified in the constructor. If it was not specified in the constructor, use the default httpClient. HttpClient httpClient = _httpClient ?? DefaultHttpClient; try { // Check if App Services MSI is available. If both these environment variables are set, then it is. string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT"); string msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET"); var isAppServicesMsiAvailable = !string.IsNullOrWhiteSpace(msiEndpoint) && !string.IsNullOrWhiteSpace(msiSecret); // if App Service MSI is not available then Azure VM IMDS must be available, verify with a probe request if (!isAppServicesMsiAvailable) { using (var internalTokenSource = new CancellationTokenSource()) using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(internalTokenSource.Token, cancellationToken)) { HttpRequestMessage imdsProbeRequest = new HttpRequestMessage(HttpMethod.Get, AzureVmImdsEndpoint); try { internalTokenSource.CancelAfter(AzureVmImdsTimeout); await httpClient.SendAsync(imdsProbeRequest, linkedTokenSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { // request to IMDS timed out (internal cancellation token cancelled), neither Azure VM IMDS nor App Services MSI are available if (internalTokenSource.Token.IsCancellationRequested) { throw new HttpRequestException(); } throw; } } } // If managed identity is specified, include client ID parameter in request string clientIdParameterName = isAppServicesMsiAvailable ? "clientid" : "client_id"; string clientIdParameter = _managedIdentityClientId != default(string) ? $"&{clientIdParameterName}={_managedIdentityClientId}" : string.Empty; // Craft request as per the MSI protocol var requestUrl = isAppServicesMsiAvailable ? $"{msiEndpoint}?resource={resource}{clientIdParameter}&api-version=2017-09-01" : $"{AzureVmImdsEndpoint}?resource={resource}{clientIdParameter}&api-version=2018-02-01"; Func <HttpRequestMessage> getRequestMessage = () => { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); if (isAppServicesMsiAvailable) { request.Headers.Add("Secret", msiSecret); } else { request.Headers.Add("Metadata", "true"); } return(request); }; HttpResponseMessage response = await httpClient.SendAsyncWithRetry(getRequestMessage, cancellationToken).ConfigureAwait(false); // If the response is successful, it should have JSON response with an access_token field if (response.IsSuccessStatusCode) { PrincipalUsed.IsAuthenticated = true; string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); // Parse the JSON response TokenResponse tokenResponse = TokenResponse.Parse(jsonResponse); AccessToken token = AccessToken.Parse(tokenResponse.AccessToken); // If token is null, then there has been a parsing issue, which means the access token format has changed if (token != null) { PrincipalUsed.AppId = token.AppId; PrincipalUsed.TenantId = token.TenantId; } TokenResponse.DateFormat expectedDateFormat = isAppServicesMsiAvailable ? TokenResponse.DateFormat.DateTimeString : TokenResponse.DateFormat.Unix; return(AppAuthenticationResult.Create(tokenResponse, expectedDateFormat)); } string exceptionText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new Exception($"MSI ResponseCode: {response.StatusCode}, Response: {exceptionText}"); } catch (HttpRequestException) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.RetryFailure} {AzureServiceTokenProviderException.MsiEndpointNotListening}"); } catch (Exception exp) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.GenericErrorMessage} {exp.Message}"); } }
public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority, CancellationToken cancellationToken = default(CancellationToken)) { try { // Validate resource, since it gets sent as a command line argument to Visual Studio token provider. ValidationHelper.ValidateResource(resource); _visualStudioTokenProviderFile = _visualStudioTokenProviderFile ?? GetTokenProviderFile(); // Get process start infos based on Visual Studio token providers var processStartInfos = GetProcessStartInfos(_visualStudioTokenProviderFile, resource, UriHelper.GetTenantByAuthority(authority)); // To hold reason why token could not be acquired per token provider tried. Dictionary <string, string> exceptionDictionary = new Dictionary <string, string>(); foreach (var startInfo in processStartInfos) { try { // For each of them, try to get token string response = await _processManager .ExecuteAsync(new Process { StartInfo = startInfo }, cancellationToken) .ConfigureAwait(false); TokenResponse tokenResponse = TokenResponse.Parse(response); AccessToken token = AccessToken.Parse(tokenResponse.AccessToken); PrincipalUsed.IsAuthenticated = true; if (token != null) { // Set principal used based on the claims in the access token. PrincipalUsed.UserPrincipalName = !string.IsNullOrEmpty(token.Upn) ? token.Upn : token.Email; PrincipalUsed.TenantId = token.TenantId; } return(AppAuthenticationResult.Create(tokenResponse)); } catch (Exception exp) { // If token cannot be acquired using a token provider, try the next one exceptionDictionary[Path.GetFileName(startInfo.FileName)] = exp.Message; } } // Could not acquire access token, throw exception string message = string.Empty; // Include exception details for each token provider that was tried foreach (string key in exceptionDictionary.Keys) { message += Environment.NewLine + $"Exception for Visual Studio token provider {key} : {exceptionDictionary[key]} "; } // Throw exception if none of the token providers worked throw new Exception(message); } catch (Exception exp) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.VisualStudioUsed} {AzureServiceTokenProviderException.GenericErrorMessage} {exp.Message}"); } }
public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority, CancellationToken cancellationToken = default) { MsiEnvironment?msiEnvironment = null; try { // Check if App Services MSI or Service Fabric MSI are available. Both use different set of shared env vars. var msiEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT"); var msiHeader = Environment.GetEnvironmentVariable("IDENTITY_HEADER"); _serviceFabricMsiThumbprint = Environment.GetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT"); // only in Service Fabric, needed to create HttpClient var serviceFabricApiVersion = Environment.GetEnvironmentVariable("IDENTITY_API_VERSION"); // only in Service Fabric var endpointAndHeaderAvailable = !string.IsNullOrWhiteSpace(msiEndpoint) && !string.IsNullOrWhiteSpace(msiHeader); var thumbprintAndApiVersionAvailable = !string.IsNullOrWhiteSpace(_serviceFabricMsiThumbprint) && !string.IsNullOrWhiteSpace(serviceFabricApiVersion); if (endpointAndHeaderAvailable) { msiEnvironment = thumbprintAndApiVersionAvailable ? MsiEnvironment.ServiceFabric : MsiEnvironment.AppServices; } // if App Service and Service Fabric MSI is not available then Azure VM IMDS may be available, test with a probe request if (msiEnvironment == null) { using (var internalTokenSource = new CancellationTokenSource()) using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(internalTokenSource.Token, cancellationToken)) { string probeRequestUrl = $"{ImdsEndpoint}{ImdsInstanceRoute}?api-version={ImdsInstanceApiVersion}"; HttpRequestMessage imdsProbeRequest = new HttpRequestMessage(HttpMethod.Get, probeRequestUrl); imdsProbeRequest.Headers.Add(AzureVMImdsHeader, "true"); try { internalTokenSource.CancelAfter(AzureVmImdsProbeTimeout); await HttpClient.SendAsync(imdsProbeRequest, linkedTokenSource.Token).ConfigureAwait(false); msiEnvironment = MsiEnvironment.Imds; } catch (OperationCanceledException) { // request to IMDS timed out (internal cancellation token cancelled), neither Azure VM IMDS nor App Services MSI are available if (internalTokenSource.Token.IsCancellationRequested) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.MetadataEndpointNotListening}"); } throw; } } } #if net452 if (msiEnvironment == MsiEnvironment.ServiceFabric) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} Service Fabric MSI not supported on .NET 4.5.2"); } #endif // If managed identity is specified, include client ID parameter in request string clientIdParameter = _managedIdentityClientId != default ? $"&client_id={_managedIdentityClientId}" : string.Empty; // endpoint and API version dependent on environment string endpoint = null, apiVersion = null; switch (msiEnvironment) { case MsiEnvironment.AppServices: endpoint = msiEndpoint; apiVersion = AppServicesApiVersion; break; case MsiEnvironment.Imds: endpoint = $"{ImdsEndpoint}{ImdsTokenRoute}"; apiVersion = ImdsTokenApiVersion; break; case MsiEnvironment.ServiceFabric: endpoint = msiEndpoint; apiVersion = serviceFabricApiVersion; break; } // Craft request as per the MSI protocol var requestUrl = $"{endpoint}?resource={resource}{clientIdParameter}&api-version={apiVersion}"; HttpRequestMessage getRequestMessage() { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); switch (msiEnvironment) { case MsiEnvironment.AppServices: request.Headers.Add(AppServicesHeader, msiHeader); break; case MsiEnvironment.Imds: request.Headers.Add(AzureVMImdsHeader, "true"); break; case MsiEnvironment.ServiceFabric: request.Headers.Add(ServiceFabricHeader, msiHeader); break; } return(request); } HttpResponseMessage response; try { response = await HttpClient.SendAsyncWithRetry(getRequestMessage, _retryTimeoutInSeconds, cancellationToken).ConfigureAwait(false); } catch (HttpRequestException) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.RetryFailure} {AzureServiceTokenProviderException.MsiEndpointNotListening}"); } // If the response is successful, it should have JSON response with an access_token field if (response.IsSuccessStatusCode) { PrincipalUsed.IsAuthenticated = true; string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); // Parse the JSON response TokenResponse tokenResponse = TokenResponse.Parse(jsonResponse); AccessToken token = AccessToken.Parse(tokenResponse.AccessToken); // If token is null, then there has been a parsing issue, which means the access token format has changed if (token != null) { PrincipalUsed.AppId = token.AppId; PrincipalUsed.TenantId = token.TenantId; } return(AppAuthenticationResult.Create(tokenResponse)); } string errorStatusDetail = response.IsRetryableStatusCode() ? AzureServiceTokenProviderException.RetryFailure : AzureServiceTokenProviderException.NonRetryableError; string errorText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new Exception($"{errorStatusDetail} MSI ResponseCode: {response.StatusCode}, Response: {errorText}"); } catch (Exception exp) { if (exp is AzureServiceTokenProviderException) { throw; } throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.GenericErrorMessage} {exp.Message}"); } }
/// <summary> /// Get access token by authenticating to Azure AD using Integrated Windows Authentication (IWA), /// when domain is synced with Azure AD tenant. /// </summary> /// <param name="resource">Resource to access.</param> /// <param name="authority">Authority where resource is present.</param> /// <returns></returns> public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority, CancellationToken cancellationToken = default) { // If authority is not specified, start with common. Once known, after the first time token is acquired, use that. if (string.IsNullOrWhiteSpace(authority)) { authority = string.IsNullOrEmpty(_currentUserTenant) ? $"{_azureAdInstance}common" : $"{_azureAdInstance}{_currentUserTenant}"; } // Use ADAL's default token cache, instead of file based cache. // This prevents dependency on file and DPAPI, and enables service account scenarios. AppAuthenticationResult authResult = null; try { // See if token is present in cache authResult = await _authenticationContext.AcquireTokenSilentAsync(authority, resource, ClientId).ConfigureAwait(false); } catch { // If fails, use AcquireTokenAsync } // If token not in cache, acquire it if (authResult == null) { // This causes ADAL to use IWA UserCredential userCredential = new UserCredential(); try { authResult = await _authenticationContext.AcquireTokenAsync(authority, resource, ClientId, userCredential).ConfigureAwait(false); } catch (Exception exp) { string message = $"{AzureServiceTokenProviderException.ActiveDirectoryIntegratedAuthUsed} " + $"{AzureServiceTokenProviderException.GenericErrorMessage} " + $"{exp.Message}"; if (exp.InnerException != null) { message += $"Inner Exception : {exp.InnerException.Message}"; } throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, message); } } var accessToken = authResult?.AccessToken; if (accessToken != null) { AccessToken token = AccessToken.Parse(accessToken); PrincipalUsed.UserPrincipalName = !string.IsNullOrEmpty(token.Upn) ? token.Upn : token.Email; PrincipalUsed.TenantId = _currentUserTenant = token.TenantId; PrincipalUsed.IsAuthenticated = true; return(authResult); } // If result is null, token could not be acquired, and no exception was thrown. throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, AzureServiceTokenProviderException.GenericErrorMessage); }
public override async Task <AppAuthenticationResult> GetAuthResultAsync(string resource, string authority) { try { // Check if App Services MSI is available. If both these environment variables are set, then it is. string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT"); string msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET"); var isAppServicesMsiAvailable = !string.IsNullOrWhiteSpace(msiEndpoint) && !string.IsNullOrWhiteSpace(msiSecret); // If managed identity is specified, include client_id parameter in request string clientIdParameter = _managedIdentityClientId != default(string) ? $"&client_id={_managedIdentityClientId}" : string.Empty; // Craft request as per the MSI protocol var requestUrl = isAppServicesMsiAvailable ? $"{msiEndpoint}?resource={resource}&api-version=2017-09-01" : $"{AzureVmIdmsEndpoint}?resource={resource}{clientIdParameter}&api-version=2018-02-01"; // Use the httpClient specified in the constructor. If it was not specified in the constructor, use the default httpclient. HttpClient httpClient = _httpClient ?? DefaultHttpClient; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); if (isAppServicesMsiAvailable) { request.Headers.Add("Secret", msiSecret); } else { request.Headers.Add("Metadata", "true"); } HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false); // If the response is successful, it should have JSON response with an access_token field if (response.IsSuccessStatusCode) { PrincipalUsed.IsAuthenticated = true; string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); // Parse the JSON response TokenResponse tokenResponse = TokenResponse.Parse(jsonResponse); AccessToken token = AccessToken.Parse(tokenResponse.AccessToken); // If token is null, then there has been a parsing issue, which means the access token format has changed if (token != null) { PrincipalUsed.AppId = token.AppId; PrincipalUsed.TenantId = token.TenantId; } TokenResponse.DateFormat expectedDateFormat = isAppServicesMsiAvailable ? TokenResponse.DateFormat.DateTimeString : TokenResponse.DateFormat.Unix; return(AppAuthenticationResult.Create(tokenResponse, expectedDateFormat)); } string exceptionText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new Exception($"MSI ResponseCode: {response.StatusCode}, Response: {exceptionText}"); } catch (HttpRequestException) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.MsiEndpointNotListening}"); } catch (Exception exp) { throw new AzureServiceTokenProviderException(ConnectionString, resource, authority, $"{AzureServiceTokenProviderException.ManagedServiceIdentityUsed} {AzureServiceTokenProviderException.GenericErrorMessage} {exp.Message}"); } }