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);
        }
Exemple #2
0
        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}");
            }
        }
Exemple #3
0
        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}");
            }
        }