I have two projects: a Web API project and a client project.
In the client application, I configure my HttpClient like this.
services.AddHttpClient<TrackAndTraceClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri(settings.BaseUrl);
httpClient.Timeout = TimeSpan.FromMinutes(5);
})
.ConfigurePrimaryHttpMessageHandler(serviceProvider =>
{
return new HttpClientHandler()
{
Credentials = new NetworkCredential(settings.Username, settings.Password),
};
});
And then in my class that calls the API:
public TrackAndTraceClient(IHttpClientFactory httpClientFactory, IOptions<TrackAndTraceSettings> settings)
{
HttpClient = httpClientFactory.CreateClient(nameof(TrackAndTraceClient));
Settings = settings.Value;
}
My Web API site implements basic authentication using the techniques described in this article. But my code throws an exception because no Authorization header is found.
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// ...
if (!Request.Headers.TryGetValue("Authorization", out StringValues authHeaderValues))
throw new Exception("Missing Authorization header");
// ...
}
}
I can only get this to work by adding the following code to my class that calls the API:
HttpClient.DefaultRequestHeaders.Add("ContentType", "application/json");
byte[] credentialsData = Encoding.UTF8.GetBytes($"{Settings.Username}:{Settings.Password}");
string credentials = Convert.ToBase64String(credentialsData);
HttpClient.DefaultRequestHeaders.Add("Authorization", $"Basic {credentials}");
Can anyone tell me why this last block of code is needed? Why doesn't setting the credentials with NetworkCredential appear to do anything? And how can I change my Web API so that it works with credentials specified the original way?
Note that I'm also calling a third-party API, and the client is configured exactly the same way as in my first block of code. So I know that can be made to work.
CodePudding user response:
From our conversation in the comments section -
The implementation of your BasicAuthenticationHandler lacks adding the WWW-Authenticate HTTP header (with value Basic).
That is the header upon which the HttpClient reacts to include the Authorization HTTP header when it receives a 401 Unauthorized response.
To resolve the issue, add the line below to the BasicAuthenticationHandler.
Response.Headers.Add("WWW-Authenticate", "Basic");
Now the NetworkCredentials will go into the Authorization HTTP header.
In short without being complete about how this works;
when a HttpClient makes a request (without Authorization header), and receives a 401 Unauthorized response in combination with a WWW-Authenticate HTTP header, it will make a 2nd attempt with the configured credentials - if any - in the Authorization HTTP header.
For simplicity, you might want to include the Authorization at once, without relying on NetworkCredentials and WWW-Authenticate HTTP headers.
services.AddHttpClient<TrackAndTraceClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri(settings.BaseUrl);
httpClient.Timeout = TimeSpan.FromMinutes(5);
// Add below to your existing code.
var digest = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{settings.Username}:{settings.Password}")
);
httpClient.DefaultRequestHeaders.Add("Authorization", $"Basic {digest}");
HttpClient.DefaultRequestHeaders.Add("ContentType", "application/json");
});
