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); var clientIdParameterName = isAppServicesMsiAvailable ? "clientid" : "client_id"; // If managed identity is specified, include client_id parameter in request 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" : $"{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}"); } }
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); 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}"); } }