internal static AppAuthenticationResult Create(TokenResponse response, TokenResponse.DateFormat dateFormat) { if (response == null) { throw new ArgumentNullException(nameof(response)); } var expiresOnString = response.ExpiresOn ?? response.ExpiresOn2; var expiresOn = DateTimeOffset.MinValue; switch (dateFormat) { case TokenResponse.DateFormat.DateTimeString: expiresOn = DateTimeOffset.Parse(expiresOnString); break; case TokenResponse.DateFormat.Unix: var seconds = double.Parse(expiresOnString); expiresOn = Models.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); }
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) { 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}"); } }