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}");
}
}
}
}
}
}