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