Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting Authorization header on an HttpClient instance does not work in .NET Core 2.1 #26475

Closed
Petermarcu opened this issue Jun 13, 2018 · 17 comments
Labels
bug tenet-compatibility Incompatibility with previous versions or .NET Framework
Milestone

Comments

@Petermarcu
Copy link
Member

@pereiraarun commented on Mon Jun 11 2018

Testing on .NET Core 2.1 (by setting Target Framework 2.1), the following code results in a 403 Forbidden since the header is not set correctly.
Header is set using the following method:

    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "9ac..b87b20");
    var result = client.GetStringAsync("/api/ipam/prefixes/").Result;

Moving back to .NET Core 2.0 (by setting Target Framework 2.0) fixes the issue. The same happens when using RestSharp. Seems there is some bug with HttpClient and setting authorization headers.

Testing through: Vs Professional 2017 (15.7.3) on Windows 10 with the latest updates.


@brockallen commented on Mon Jun 11 2018

Are you sure the scheme is correct? Normally it should be "Bearer" (not "Token") if you're doing an OAuth2 style client.


@pereiraarun commented on Tue Jun 12 2018

The code works as posted in .Net Core 2.0. The code is used for https://netbox.readthedocs.io/en/latest/api/authentication/

$ curl -H "Authorization: Token d2f763479f703d80de0ec15254237bc651f9cdc0" -H "Accept: application/json; indent=4" http://localhost/api/dcim/sites/
{
"count": 10,
"next": null,
"previous": null,
"results": [...]
}

@MykolaBalakin
Copy link

MykolaBalakin commented Jun 13, 2018

@Petermarcu, could you provide a code to reproduce the issue?
The code:

class Program
{
    static async Task Main(string[] args)
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("http://example.com");
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "9ac..b87b20");
        var result = await client.GetStringAsync("/api/ipam/prefixes/");
        Console.WriteLine(result);
    }
}

generates a request with authorization header filled:

GET http://example.com/api/ipam/prefixes/ HTTP/1.1
Authorization: Token 9ac..b87b20
Host: example.com

@davidsh
Copy link
Contributor

davidsh commented Jun 13, 2018

Testing on .NET Core 2.1 (by setting Target Framework 2.1), the following code results in a 403 Forbidden since the header is not set correctly.

@pereiraarun

Is there a way we can repro this problem? I don't see any problem with the APIs that set the 'Authorization' header. Can you provide some traces to show exactly what the headers are being set to if they are being perceived as "not set correctly"?

@pereiraarun
Copy link

pereiraarun commented Jun 13, 2018

Sure. Will do when I get a chance. I realize I was being vague with my bug report.

Incidentally, the code posted by @nbalakin above actually works through LinqPad but not with a .net core 2.1 project.

@davidsh
Copy link
Contributor

davidsh commented Jul 26, 2018

@pereiraarun

We are unable to reproduce the problem. Using the code above generates a request with the right headers. So, perhaps the problem is that server is having issues validating the request headers.

For now, we'll close this issue. If you have repro that we can run to demonstrate that invalid headers are being sent by HttpClient, then we can re-open the issue. Thx.

@davidsh davidsh closed this as completed Jul 26, 2018
@BCatBB
Copy link

BCatBB commented Aug 21, 2018

I have the same issue using 'Bearer'. worked in 2.0, fails no matter what I do in 2.1. I'm forced to roll everything back to 2.0. IMHO Core 2.1 is not ready for prime time. I will be staying away from it for at least the rest of the year.

@azampagl
Copy link

azampagl commented Aug 22, 2018

+1 this issue. Didn't have it it 2.0 but now have it in 2.1.

It seems to work fine when PUTing/POSTing to another .NET Core application. When posting to a .NET Framework (4.6) project the following occurs:

Code:

client.Timeout = new TimeSpan(0, 0, REQUEST_TIMEOUT_S);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token); // I've tried "Token" as well.
var response = await client.PutAsync(Uri, new StringContent(data, Encoding.UTF8, "application/json"));
if (!response.IsSuccessStatusCode)
{
   var errorResponse = await response.Content.ReadAsStringAsync();
   return false;
}

Server side, I explicitly throw an exception and iterate through the headers. Notice authorization is not even there.

"errorResponse":

Content-Length= 2239, Content-Type= application/json; charset=utf-8, Cookie= ASP.NET_SessionId=<sessionidstring>, Host= mydomain.com, Request-Context= appId=<appidstring>, Request-Id= <requestidstring>

@christiaanverwijs
Copy link

christiaanverwijs commented Aug 28, 2018

+1 for me. I have also have this issue in this code (which used to work in 2.0):

                client.Timeout = new TimeSpan(0, 0, 120);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", siteSettings.EventBriteOAuthKey);
                
                var responseMessage = await client.GetAsync(url);
                var data = await responseMessage.Content.ReadAsStringAsync();

The bearer token is not actually added to the request. This issue is occuring when posting to EventBrite's API in this case.

@chrisipeters
Copy link

chrisipeters commented Oct 24, 2018

+1 for me on 2.1.403. Forgive the code, I've been trying to track down the issue before running into this thread:

Checking on the code in debug:

  • on client the authorization header is present
  • on res.RequestMessage - the Test header is present, but not the Authorization header.
  • the commented line did not work either, interestingly though, if both it and the line above are left un-commented, An exception is thrown:
    Cannot add value because header 'Authorization' does not support multiple values.
var client = new HttpClient();
var _authHeader = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "username", "password"))));

client.DefaultRequestHeaders.Authorization = _authHeader;
var req = new HttpRequestMessage(HttpMethod.Get, _apiUrl);
req.Headers.Authorization = _authHeader;
// req.Headers.Add("Authorization", $"{_authHeader.Scheme} {_authHeader.Parameter}");
req.Headers.Add("Test", $"{_authHeader.Scheme} {_authHeader.Parameter}");

var res = await client.SendAsync(req);

I've gone back and tried the code as outlined in https://github.com/dotnet/corefx/issues/30349#issuecomment-396885353 above, but that did not work either.

@davidsh
Copy link
Contributor

davidsh commented Oct 24, 2018

@karelz

@rtsigler
Copy link

rtsigler commented Oct 24, 2018

I had the same problem and found it was related to an automatic redirect. For a temporary fix, I was able to use the URL I was being redirected to instead. It seems like the authentication header is being lost during the redirect.

If you disable AllowAutoRedirect on the HTTP client, can you check if you're being redirected?

var handler = new HttpClientHandler()
{
        AllowAutoRedirect = false                
};
var client = new HttpClient(handler);

@karelz
Copy link
Member

karelz commented Oct 24, 2018

Does anyone have a repro you can share with us, so that we can try it locally?
Aren't redirects expected to drop authentication header? (from security reasons)

@davidsh
Copy link
Contributor

davidsh commented Oct 24, 2018

Aren't redirects expected to drop authentication header? (from security reasons)

Yes. That behavior is by-design. 'Authorization' request headers are removed during redirects.

@davidsh
Copy link
Contributor

davidsh commented Oct 24, 2018

Yes. That behavior is by-design. 'Authorization' request headers are removed during redirects.

There are ways to preserve them though. That requires using a CredentialsCache object and populating it with credentials assigned to specific Uri paths. Then, assign that object to the HttpClientHandler.Credentials property.

However, manually adding 'Authorization' request headers is not a recommended pattern anyways. And those headers will be removed during redirects.

@karelz
Copy link
Member

karelz commented Oct 25, 2018

FYI: 2 weeks ago we released a security fix to remove Authorization request headers from redirects. See dotnet/corefx#32730.
.NET Core 2.0 didn't get the patch because it is out of support as of 10/1.

If anyone hits the problem without redirects being involved, please let us know. That is something we would look into. We would need repro or further details in such case to make progress.

@chrisipeters
Copy link

chrisipeters commented Oct 25, 2018

Thanks all, the security change about removing Authorization headers is in fact what was going on in my case. For those still working through it, here's the code I have - working now:
Adapted from: https://stackoverflow.com/a/28671822/5043701

  • Only does GET in my case
private async Task<string> MakeHTTPCall(Uri url, AuthenticationHeaderValue authHeader)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = authHeader;

            var response = await client.GetAsync(url);

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                // Authorization header has been set, but the server reports that it is missing.
                // It was probably stripped out due to a redirect.

                var finalRequestUri = response.RequestMessage.RequestUri; // contains the final location after following the redirect.

                if (finalRequestUri != url) // detect that a redirect actually did occur.
                {
                    // If this is public facing, add tests here to determine if Url should be trusted
                    response = await client.GetAsync(finalRequestUri);
                }
            }

            return await response.Content.ReadAsStringAsync();
        }

@MelbourneDeveloper
Copy link

MelbourneDeveloper commented Dec 27, 2019

@karelz , I understand why the security fix was added, but doesn't this raise another important issue? What if there is some other sensitive header included in the original request. Won't that get sent as part of the redirect?

Shouldn't there be a callback on HttpClient or the HttpClientHandler that exposes the headers so that we can add or remove them as necessary?

Are we meant to write handler code on every http call that may redirect as @chrisipeters has demonstrated? That's very onerous and only deals with the problem after the fact.

PS: This has probably been going on since the early versions of HttpClient / HttpClientHandler and probably has implications for all the different platforms. I think I'm experiencing headers being stripped because of redirects in .NET 4.5. What is Microsoft's recommended approach to this, and are there long term plans to add a callback to that this problem can be dealt with in a graceful way?

@rugglcon
Copy link

@MelbourneDeveloper I believe Microsoft's official solution for this at the moment of writing this comment (found on MSDN) is to write your own authentication module, which is not ideal. Our request to a url has a redirect that changes every year, sometimes more than once so it's unreasonable to use CredentialsCache for our use case.

In my opinion, an option should just be added to not remove headers on redirect.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 16, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug tenet-compatibility Incompatibility with previous versions or .NET Framework
Projects
None yet
Development

No branches or pull requests