Below is the code that does authentication, generates the Authorization header, and calls the API.
Unfortunately, I get a 401 Unauthorized
error following the GET
request on the API.
However, when I capture the traffic in Fiddler and replay it, the call to the API is successful and I can see the desired 200 OK
status code.
[Test]
public void RedirectTest()
{
HttpResponseMessage response;
var client = new HttpClient();
using (var authString = new StringContent(@"{username: ""theUser"", password: ""password""}", Encoding.UTF8, "application/json"))
{
response = client.PostAsync("http://host/api/authenticate", authString).Result;
}
string result = response.Content.ReadAsStringAsync().Result;
var authorization = JsonConvert.DeserializeObject<CustomAutorization>(result);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Token);
client.DefaultRequestHeaders.Add("Accept", "application/vnd.host+json;version=1");
response =
client.GetAsync("http://host/api/getSomething").Result;
Assert.True(response.StatusCode == HttpStatusCode.OK);
}
When I run this code the Authorization header is lost.
However, in Fiddler that header is passed successfully.
Any idea what I'm doing wrong?
I would turn off the automatic redirect behavior and create a client hander that hides the code dealing with the temporary redirect. The
HttpClient
class allows you to installDelegatingHandler
s from which you can modify the request of response.You would instantiate the HttpClient like this:
The reason you are experiencing this behavior is that it is by design.
Most HTTP clients (by default) strip out authorization headers when following a redirect.
One reason is security. The client could be redirected to an untrusted third party server, one that you would not want to disclose your authorization token to.
What you can do is detect that the redirect has occurred and reissue the request directly to the correct location.
Your API is returning
401 Unauthorized
to indicate that the authorization header is missing (or incomplete). I will assume that the same API returns403 Forbidden
if the authorization information is present in the request but is simply incorrect (wrong username / password).If this is the case, you can detect the 'redirect / missing authorization header' combination and resend the request.
Here is the code from the question rewritten to do this:
Note that you could save the value of
finalRequestUri
and use it for future requests to avoid the extra request involved in the retry. However as this is a temporary redirect you should probably issue the request to the original location each time.I had a similar problem, but not quite the same. In my case, I also had the redirect problem, but security is implemented with OAuth, which also has the secondary, but related, problem that tokens sometimes expire.
For that reason, I'd like to be able to configure an
HttpClient
to automatically go and refresh the OAuth token when it receives a401 Unauthorized
response, regardless of whether this happens because of a redirect, or a token expiration.The solution posted by Chris O'Neill shows the general steps to take, but I wanted to embed that behaviour inside of an
HttpClient
object, instead of having to surround all our HTTP code with an imperative check. We have a lot of existing code that uses a sharedHttpClient
object, so it'd be much easier to refactor our code if I could change the behaviour of that object.The following looks like it's working. I've only prototyped it so far, but it seems to be working. Much of our code base is in F#, so the code is in F#:
This is a little class that takes care of refreshing the
Authorization
header if it receives a401 Unauthorized
response. It refreshes using an injectedrefreshAuth
function, which has the typeunit -> Headers.AuthenticationHeaderValue
.Since this is still prototype code, I made the inner
SendAsync
call a blocking call, thereby leaving it as an exercise to the reader to implement it properly using an async workflow.Given a refresh function called
refreshAuth
, you can create a newHttpClient
object like this:The answer posted by Chris O'Neill takes care to check that the new URL is still considered safe. I skipped that security consideration here, but you should strongly consider including a similar check before retrying the request.