1

Due to the fact that HttpWebRequest is obsolete I'm in the process of upgrading to the equivalent in .NET 6.

I'm not understanding why I'm getting a 401 unauthorized. I can get the bearer token successfully but I just can't seem to find the right combination to successfully upload the file.

private static async Task UploadCsvFileAsync(string authToken)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri("https://subdomain.domain.com/");
        client.DefaultRequestHeaders
            .Accept
            .Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        using (var request = new HttpRequestMessage())
        {
            request.RequestUri = new Uri(uploadUrl);
            request.Method = HttpMethod.Post;
            request.Headers.Add("api-version", "1");
            request.Headers.Add("Authorization", $"Bearer {authToken}");

            string header = $"{Environment.NewLine}--{formBoundary}{Environment.NewLine}" +
                $"Content-Disposition: form-data; name=\"csvfile\"; filename=\"{csvFileName}\"{Environment.NewLine}" +
                $"Content-Type: text/csv{Environment.NewLine}{Environment.NewLine}";
            byte[] headerBytes = Encoding.UTF8.GetBytes(header);
            var headerContent = new StreamContent(new MemoryStream(headerBytes));
            headerContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
            var content = new MultipartFormDataContent();
            content.Add(headerContent, "metadata", csvFileName);

            var response = await client.PostAsync(uploadUrl, content);
            // the response code is 401 - Unauthorized
        }
    }
}

I have working .NET Framework 4.x code working. Since we're migrating our code to .NET 6 it has to be re-written.

Obviously I'm doing something wrong - I just can't seem to figure out what I'm missing.

Any help is greatly appreciated. Thank you in advance.

Edit: 03/29/2023 - including correct code as provided by answer below.

private static async Task UploadCsvFileAsyncV2(string authToken)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(uploadUrl);
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
        client.DefaultRequestHeaders
            .Accept
            .Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        using (var request = new HttpRequestMessage(HttpMethod.Post, "uploads"))
        {
            request.Headers.Add("api-version", "1");
            string header = $"{Environment.NewLine}--{formBoundary}{Environment.NewLine}" +
                $"Content-Disposition: form-data; name=\"csvfile\"; filename=\"{csvFileName}\"{Environment.NewLine}" +
                $"Content-Type: text/csv{Environment.NewLine}{Environment.NewLine}";
            byte[] headerBytes = Encoding.UTF8.GetBytes(header);
            var headerContent = new StreamContent(new MemoryStream(headerBytes));
            headerContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
            var content = new MultipartFormDataContent
            {
                { headerContent, "metadata", csvFileName }
            };
            request.Content = content;

            var response = await client.SendAsync(request);
        }
    }
}
OneClutteredMind
  • 369
  • 1
  • 3
  • 15
  • Does this answer your question? [Setting Authorization Header of HttpClient](https://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient) – Charlieface Mar 28 '23 at 22:42
  • @Charlieface that's for basic login/password auth. This is bearer token/jwt. – Emperor Eto Mar 29 '23 at 01:22
  • @JustAnswertheQuestion Don't think you're right about that, the docs seem to indicate that should work https://learn.microsoft.com/en-us/dotnet/api/system.net.http.headers.authenticationheadervalue.-ctor?view=net-7.0#system-net-http-headers-authenticationheadervalue-ctor(system-string-system-string) – Charlieface Mar 29 '23 at 09:40
  • @Charlieface there are different types of authentication methods. "Basic" uses a base64 concatenation of the username and password. "Bearer" just uses the JWT as-is. Yes, using `AuthenticationHeaderValue` is fine but the arguments would still be `"Bearer"` and `authToken`, no additional base64 conversion needed. But the way he has it is fine too and isn't the reason he's getting the 401. – Emperor Eto Mar 29 '23 at 16:31

1 Answers1

1

You're not using HttpClient quite right. Notice how you declare the HttpRequestMessage - where you add the auth header - but never actually use it? You're sort of mixing the old method with the new one and not actually sending out the auth token.

If you want to use HttpRequestMessage, which is fine, just use HttpClient.SendAsync and supply the message. Specifically try this after byte[] headerBytes = Encoding.UTF8.GetBytes(header);:

      request.Headers.Add("Content-Type", "application/octet-stream");
      var content = new MultipartFormDataContent();                  
      content.Add(new StreamContent(new MemoryStream(headerBytes)), "metadata", csvFileName);
      request.Content = content;
      var response = await client.SendAsync(request);
Emperor Eto
  • 2,456
  • 2
  • 18
  • 32
  • 1
    Thank you! That did the trick. I guess trying so many things I completely missed adding the request. Can you explain what you mean by, "You're sort of mixing the old method with the new one..." I would like to understand to avoid using the old method. Thanks again! – OneClutteredMind Mar 29 '23 at 17:46
  • Great! Sure I just mean that it's less common to use `HttpRequestMessage` now, at least that's my impression. `HttpClient` has `PostAsync` and such which scaffold much of the message for you so you don't have to make your own `HttpRequestMessage`. But in your case where you're injecting the headers on your own, that's pretty low level, so you just use `SendAsync` which takes the message rather than the more abstract `PostAsync`, etc., methods. Maybe I should have said you were previously mixing the high level and low level approaches, so instead just stick to low level. – Emperor Eto Mar 29 '23 at 20:37