protected override async Task AcquireTokenCoreAsync()

in wwauth/Google.Solutions.WWAuth/Adapters/Adfs/AdfsOidcAdapter.cs [123:277]


        protected override async Task<ISubjectToken> AcquireTokenCoreAsync(
            CancellationToken cancellationToken)
        {
            //
            // Fetch the OIDC configuration. This implicitly validates the
            // issuer URL and provides us the token endpoint.
            //
            var configuration = await FetchOidcConfigurationAsync(cancellationToken)
                .ConfigureAwait(false);

            this.Logger.Info("Using token endpoint '{0}'", configuration.TokenEndpoint);

            //
            // Request a token from the token endpoint.
            //
            using (var handler = new HttpClientHandler())
            {
                handler.Credentials = this.Credential;

                using (var client = new HttpClient(handler))
                {
                    this.Logger.Info(
                        "Acquiring OIDC token for client ID '{0}' " +
                            "and resource '{1}' using IWA",
                        this.ClientId,
                        this.Resource);

                    //
                    // AD FS occasionally fails requests for no good reason, so use
                    // a backoff/retry loop.
                    //
                    var backoff = new ExponentialBackOff();
                    for (int retries = 0; ; retries++)
                    {
                        var request = new HttpRequestMessage(HttpMethod.Post, configuration.TokenEndpoint)
                        {
                            Content = new FormUrlEncodedContent(new Dictionary<string, string>
                                {
                                    { "client_id", this.ClientId },
                                    { "resource", this.Resource },
                                    { "grant_type", "client_credentials" },
                                    { "use_windows_client_authentication", "true" }, // Use IWA, see [OS-OAPX].
                                    { "scope", "openid" }
                                })
                        };

                        //
                        // Set a custom user-agent so that AD FS lets us use IWA.
                        //
                        request.Headers.Add("User-Agent", AdfsAdapterBase.IwaUserAgent);

                        using (var response = await client.SendAsync(
                                request,
                                HttpCompletionOption.ResponseHeadersRead,
                                cancellationToken)
                            .ConfigureAwait(false))
                        {
                            var responseType = response.Content?.Headers?.ContentType?.MediaType;
                            if (responseType == "application/json" &&
                                response.StatusCode == HttpStatusCode.OK)
                            {
                                //
                                // Use the existing TokenResponse class to parse
                                // the response (and handle errors).
                                //
                                this.Logger.Info("Acquiring token succeeded");
                                return JsonWebToken.FromResponse(
                                    await TokenResponse.FromHttpResponseAsync(
                                        response,
                                        SystemClock.Default,
                                        new NullLogger())
                                    .ConfigureAwait(false));
                            }
                            else if (responseType == "application/json")
                            {
                                //
                                // Request failed, but we got a proper OAuth-formatted error.
                                //
                                var error = JsonConvert.DeserializeObject<TokenErrorResponse>(
                                    await response.Content
                                        .ReadAsStringAsync()
                                        .ConfigureAwait(false));

                                throw new TokenAcquisitionException(
                                    $"Authentication failed: {error.ErrorDescription}\n" +
                                    $"Error code: {error.Error}\n" +
                                    $"HTTP Status: {response.StatusCode}");
                            }
                            else if (responseType == "text/html")
                            {
                                //
                                // AD FS returns some errors in HTML format.
                                //
                                var responseBody = await response.Content
                                    .ReadAsStringAsync()
                                    .ConfigureAwait(false);

                                this.Logger.Error(
                                    "Received unexpected response of type {0} from {1}: {2}",
                                    responseType,
                                    configuration.TokenEndpoint,
                                    responseBody);

                                var html = new HtmlResponse(responseBody);
                                if (html.Error != null)
                                {
                                    throw new TokenAcquisitionException(
                                        $"Authentication failed: {html.Error}. See logs for " +
                                        "full response message");
                                }
                                else
                                {
                                    throw new TokenAcquisitionException(
                                        "Authentication failed. The server sent an unexpected response of type " +
                                        $"{responseType}. See logs for " +
                                        "full response message");
                                }
                            }
                            else if (response.StatusCode == HttpStatusCode.Unauthorized)
                            {
                                throw new TokenAcquisitionException(
                                    $"Authentication failed. Verify that the client '{this.ClientId}' " +
                                    $"exists and is configured to allow access to the current AD user.\n\n" +
                                    "If AD FS is deployed behind a load balancer, verify that the " +
                                    "token binding settings (ExtendedProtectionTokenCheck) are compatible " +
                                    "with your load balancer setup.");
                            }
                            else if ((response.StatusCode == HttpStatusCode.BadRequest ||
                                        response.StatusCode == (HttpStatusCode)429) &&
                                    retries < backoff.MaxNumOfRetries)
                            {
                                //
                                // Retry.
                                //
                                this.Logger.Warning("Received Bad Request response, retrying");

                                await Task
                                    .Delay(backoff.DeltaBackOff)
                                    .ConfigureAwait(false);
                            }
                            else
                            {
                                //
                                // Unspecific error.
                                //
                                response.EnsureSuccessStatusCode();

                                throw new TokenAcquisitionException(
                                    $"Authentication failed: {response.StatusCode}");
                            }
                        }
                    }
                }
            }
        }