.NET Core Httpclient works but .Net Framework 4.7.2 httpclient doesn't - c#

Sorry for the awful title, I'm not really sure how to phrase my issue in a short title format.
I'm trying to communicate with an external API. I make a basic authentication request to that API and get an x-csrf-token and a session token from the api.
I then make another request to that API, now using the x-csrf-token as a header and attach the session token to the header as "cookie".
The team that maintains the API sent me an example project that handles all of the above, and it looks like this:
public static async Task<string> Send(string apiname, string value)
{
// Fetch the authorization tokens from SAP
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(basePath);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(user + ":" + password)));
client.DefaultRequestHeaders.Add("x-csrf-token", "Fetch");
string csrfToken = "";
string sessionCookie = "";
HttpResponseMessage response = await client.GetAsync(string.Empty);
IEnumerable<string> values;
if (response.Headers.TryGetValues("x-csrf-token", out values))
{
csrfToken = values.FirstOrDefault();
}
if (response.Headers.TryGetValues("set-cookie", out values))
{
sessionCookie = values.Where(s => s.StartsWith("SAP_SESSION")).FirstOrDefault();
}
// Reinstantiate the HttpClient, adding the tokens we just got from SAP
client = new HttpClient();
client.DefaultRequestHeaders.Add("x-csrf-token", csrfToken);
client.DefaultRequestHeaders.Add("cookie", sessionCookie);
client.BaseAddress = new Uri(basePath);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Have to parse the string this way otherwise it'll break the dates
JToken token;
using (var sr = new StringReader(value))
using (var jr = new JsonTextReader(sr) { DateParseHandling = DateParseHandling.None })
{
token = JToken.ReadFrom(jr);
}
HttpResponseMessage response2 = await client.PostAsJsonAsync(apiname, token);
string responseBody = await response2.Content.ReadAsStringAsync();
return responseBody;
}
This all works great as a .NET Core webAPI (and also as a .netcore console app).
Interestingly enough (in my opinion anyway), when I use the exact same code in a .net 4.7.2 project, it doesn't append the "cookie" header properly, and so I'm getting an unauthorized redirect back from the API.
To be absolutely sure that I didn't change any code, I started from scratch with a brand new .netcore 2.0 console app and a brand new .net 4.7.2 console app and copy-pasted the exact same code and installed the same nuget packages (Newtonsoft.JSON and Microsoft.WebApi.Client). I inspected my web traffic with fiddler (seen below) and you can see that in .netcore, the cookie appends properly and everything works, but in .net 4.7.2, the API returns a redirect to authenticate.

HttpClient will eat the custom cookie if you do not setUseCookies to false,
using (var handler = new HttpClientHandler { UseCookies = false })
using (client = new HttpClient(handler) { BaseAddress = new Uri(Path) }){
client.DefaultRequestHeaders.Add("cookie", cookieValue);
}
It will try to use the cookie container and at the same time ignore any custom cookie headers, very frustrating behavior if you ask me.

.Net Framework uses Cookie Container.
Also core, perhaps its a better implementation then what you are doing now and more supported.
Please see cookie container docs
Small example:
var cookieContainer = new CookieContainer();
this.handler = new HttpClientHandler
{
CookieContainer = cookieContainer,
UseCookies = true
};
client = new HttpClient(handler);

Related

Xamarin.Forms: default instead of AndroidClientHandler as HttpClient implementation

I work on an existing Xamarin.Forms app, where we need to integrate API calls to external providers.
For this, we use my client's custom HttpClient library.
Until now, the Android HttpClient was set to AndroidClientHandler.
But after a basic call to a provider's API, we get a "400 - bad request" answer:
HttpContent content = new StringContent(string.Empty);
if (tObject != null)
{
string data = JsonSerializer.Serialize(tObject, new JsonSerializerOptions() { IgnoreNullValues = true, IgnoreReadOnlyProperties = true });
content = new StringContent(data, Encoding.UTF8, "application/json");
}
HttpResponseMessage apiResponse = await _client.PostAsync(route, content);
string responseData = await apiResponse.Content.ReadAsStringAsync();
If we set the Android HttpClient to default, all works fine.
But it seems that it's better choice to use the AndroidClientHandler.
So would you have suggestions to investigate and identify the issue related to the AndroidClientHandler?

How to diagnose a 401 error attempting to get an OAuth2 bearer token in c# .NET Core?

I have some limited skills in c++ and have recently moved in C# (asp.net) and azure Web services. As a PoC I'm trying to make REST calls into PayPal (which I'll need to be using professionally in 3 -6 months).
I've set up my personal PayPal account using the instructions here and I get a bearer token back using curl as described in the link. Awesome.
I'm now trying to do this from .NET Core C# and all I get is a 401 error. I've examined the request and it seems the same as the curl in terms of headers; the base64 encoded credentials I think I'm adding are the same as the ones in the verbose curl log (I examined the two base64 strings by eye) so it must be something I'm doing (or not doing) in the set up of the call. I'm looking for suggestions, pointers, or flat out laughter at the obvious mistake I've made.
I've set up what I believe to be a named client thus:
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("PayPal", c =>
{
c.BaseAddress = new Uri("https://api.sandbox.paypal.com/v1/");
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Accept-Language", "en_US");
});
(with all the other stuff that comes free with VS under it omitted for brevity).
I attempt the call thus:
string clientCredString = CLIENTID + ":" + SECRET;
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
var client = _clientFactory.CreateClient("PayPal");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(clientCreds));
var messageBody = new Dictionary<string,string > ();
messageBody.Add("grant_type", "client_credientials");
var request = new HttpRequestMessage(HttpMethod.Get, "oauth2/token")
{
Content = new FormUrlEncodedContent(messageBody)
};
string token;
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<string>(json);
}
else
{
throw new ApplicationException("Well that failed");
}
and get a 401 code for my trouble.
Suggestions for troubleshooting, better methods of doing this and laughter at my foolishness all welcomed.
Update:
I read the documentation, a couple of items stand out to me:
Requires a verb of post.
Uses FormUrlEncodedContent for client credentials.
Basic auth requires username and password (Client Id & Secret)
I believe the syntax should be:
var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "...");
request.Content = new Dictionary<string, string>() { "grant_type", "client_credentials" };
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", $"{Encoding.UTF8.GetBytes($"{id}:{secret}")}");
HttpResponseMEssage = response = await client.PostAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
For the benefit of future readers:
It was, as suggested, an encoding problem. The line:
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
needed to be
var clientCreds = System.Text.Encoding.ASCII.GetBytes(clientCredString);
It should also be noted that this particular operation requires a POST not a GET as I was using, but once I started sending properly encoded requests the errors started to make a lot more sense.

How to add the header Xamarin Forms using System.Net.Http; [duplicate]

I'm trying to set the Content-Type header of an HttpClient object as required by an API I am calling.
I tried setting the Content-Type like below:
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri("http://example.com/");
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");
// ...
}
It allows me to add the Accept header but when I try to add Content-Type it throws the following exception:
Misused header name. Make sure request headers are used with
HttpRequestMessage, response headers with HttpResponseMessage, and
content headers with HttpContent objects.
How can I set the Content-Type header in a HttpClient request?
The content type is a header of the content, not of the request, which is why this is failing. AddWithoutValidation as suggested by Robert Levy may work, but you can also set the content type when creating the request content itself (note that the code snippet adds application/json in two places-for Accept and Content-Type headers):
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://example.com/");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT header
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "relativeAddress");
request.Content = new StringContent("{\"name\":\"John Doe\",\"age\":33}",
Encoding.UTF8,
"application/json");//CONTENT-TYPE header
client.SendAsync(request)
.ContinueWith(responseTask =>
{
Console.WriteLine("Response: {0}", responseTask.Result);
});
For those who didn't see Johns comment to carlos solution ...
req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
If you don't mind a small library dependency, Flurl.Http [disclosure: I'm the author] makes this uber-simple. Its PostJsonAsync method takes care of both serializing the content and setting the content-type header, and ReceiveJson deserializes the response. If the accept header is required you'll need to set that yourself, but Flurl provides a pretty clean way to do that too:
using Flurl.Http;
var result = await "http://example.com/"
.WithHeader("Accept", "application/json")
.PostJsonAsync(new { ... })
.ReceiveJson<TResult>();
Flurl uses HttpClient and Json.NET under the hood, and it's a PCL so it'll work on a variety of platforms.
PM> Install-Package Flurl.Http
try to use TryAddWithoutValidation
var client = new HttpClient();
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
.Net tries to force you to obey certain standards, namely that the Content-Type header can only be specified on requests that have content (e.g. POST, PUT, etc.). Therefore, as others have indicated, the preferred way to set the Content-Type header is through the HttpContent.Headers.ContentType property.
With that said, certain APIs (such as the LiquidFiles Api, as of 2016-12-19) requires setting the Content-Type header for a GET request. .Net will not allow setting this header on the request itself -- even using TryAddWithoutValidation. Furthermore, you cannot specify a Content for the request -- even if it is of zero-length. The only way I could seem to get around this was to resort to reflection. The code (in case some else needs it) is
var field = typeof(System.Net.Http.Headers.HttpRequestHeaders)
.GetField("invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
?? typeof(System.Net.Http.Headers.HttpRequestHeaders)
.GetField("s_invalidHeaders", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
if (field != null)
{
var invalidFields = (HashSet<string>)field.GetValue(null);
invalidFields.Remove("Content-Type");
}
_client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "text/xml");
Edit:
As noted in the comments, this field has different names in different versions of the dll. In the source code on GitHub, the field is currently named s_invalidHeaders. The example has been modified to account for this per the suggestion of #David Thompson.
For those who troubled with charset
I had very special case that the service provider didn't accept charset, and they refuse to change the substructure to allow it...
Unfortunately HttpClient was setting the header automatically through StringContent, and no matter if you pass null or Encoding.UTF8, it will always set the charset...
Today i was on the edge to change the sub-system; moving from HttpClient to anything else, that something came to my mind..., why not use reflection to empty out the "charset"? ...
And before i even try it, i thought of a way, "maybe I can change it after initialization", and that worked.
Here's how you can set the exact "application/json" header without "; charset=utf-8".
var jsonRequest = JsonSerializeObject(req, options); // Custom function that parse object to string
var stringContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
stringContent.Headers.ContentType.CharSet = null;
return stringContent;
Note: The null value in following won't work, and append "; charset=utf-8"
return new StringContent(jsonRequest, null, "application/json");
EDIT
#DesertFoxAZ suggests that also the following code can be used and works fine. (didn't test it myself, if it work's rate and credit him in comments)
stringContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
Some extra information about .NET Core (after reading erdomke's post about setting a private field to supply the content-type on a request that doesn't have content)...
After debugging my code, I can't see the private field to set via reflection - so I thought I'd try to recreate the problem.
I have tried the following code using .Net 4.6:
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, #"myUrl");
httpRequest.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
HttpClient client = new HttpClient();
Task<HttpResponseMessage> response = client.SendAsync(httpRequest); //I know I should have used async/await here!
var result = response.Result;
And, as expected, I get an aggregate exception with the content "Cannot send a content-body with this verb-type."
However, if i do the same thing with .NET Core (1.1) - I don't get an exception. My request was quite happily answered by my server application, and the content-type was picked up.
I was pleasantly surprised about that, and I hope it helps someone!
Call AddWithoutValidation instead of Add (see this MSDN link).
Alternatively, I'm guessing the API you are using really only requires this for POST or PUT requests (not ordinary GET requests). In that case, when you call HttpClient.PostAsync and pass in an HttpContent, set this on the Headers property of that HttpContent object.
The trick is that you can just set all kinds of headers like:
HttpRequestMessage request = new HttpRequestMessage();
request.Headers.Add("Accept-Language", "en"); //works OK
but not any header. For example:
request.Headers.Add("Content-Type", "application/json");//wrong
will raise the run-time exception Misused header name. It may seem that this will work:
request.Headers.Add(
HttpRequestHeader.ContentType.ToString(), //useless
"application/json"
);
but this gives a useless header named ContentType, without the hyphen. Header names are not case-sensitive, but are very hyphen-sensitive.
The solution is to declare the encoding and type of the body when adding the body to the Content part of the http request:
string Body = "...";
request.Content =
new StringContent(Body, Encoding.UTF8, "application/json");
Only then the applicable http header is automatically added to the request:
Content-Type: application/json; charset=utf-8
It was hard to find this out, with Fiddler, on a machine without a proxy server. Visual Studio used to have a Network Tool where you could inspect all headers, but only in version 2015, not in newer versions 2017 or 2022. If you use the debugger to inspect request.Headers, you will not find the header added automagically by StringContent().
var content = new JsonContent();
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("charset", "utf-8"));
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("IEEE754Compatible", "true"));
It's all what you need.
With using Newtonsoft.Json, if you need a content as json string.
public class JsonContent : HttpContent
{
private readonly MemoryStream _stream = new MemoryStream();
~JsonContent()
{
_stream.Dispose();
}
public JsonContent(object value)
{
Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var contexStream = new MemoryStream())
using (var jw = new JsonTextWriter(new StreamWriter(contexStream)) { Formatting = Formatting.Indented })
{
var serializer = new JsonSerializer();
serializer.Serialize(jw, value);
jw.Flush();
contexStream.Position = 0;
contexStream.WriteTo(_stream);
}
_stream.Position = 0;
}
private JsonContent(string content)
{
Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var contexStream = new MemoryStream())
using (var sw = new StreamWriter(contexStream))
{
sw.Write(content);
sw.Flush();
contexStream.Position = 0;
contexStream.WriteTo(_stream);
}
_stream.Position = 0;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return _stream.CopyToAsync(stream);
}
protected override bool TryComputeLength(out long length)
{
length = _stream.Length;
return true;
}
public static HttpContent FromFile(string filepath)
{
var content = File.ReadAllText(filepath);
return new JsonContent(content);
}
public string ToJsonString()
{
return Encoding.ASCII.GetString(_stream.GetBuffer(), 0, _stream.GetBuffer().Length).Trim();
}
}
It appears that Microsoft tries to force the developers to follow their standards, without even giving any options or settings to do otherwise, which is really a shame especially given that this is a client and we are dictated by the server side requirements, especially given that Microsoft server side frameworks themselves require it!
So basically Microsoft tries to force us good habits when connecting to their server technologies that force us non good habits...
If anyone from Microsoft is reading this, then please fix it...
Either way for anyone that needs the content-type header for Get etc., while in an older .Net version it is possible to use the answer of #erdomke at https://stackoverflow.com/a/41231353/640195 this unfortunately no longer works in the newer .Net core versions.
The following code has been tested to work with .Net core 3.1 and from the source code on GitHub it looks like it should work with newer .Net versions as well.
private void FixContentTypeHeaders()
{
var assembly = typeof(System.Net.Http.Headers.HttpRequestHeaders).Assembly;
var assemblyTypes = assembly.GetTypes();
var knownHeaderType = assemblyTypes.FirstOrDefault(n => n.Name == "KnownHeader");
var headerTypeField = knownHeaderType?
.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.FirstOrDefault(n => n.Name.Contains("HeaderType"));
if (headerTypeField is null) return;
var headerTypeFieldType = headerTypeField.FieldType;
var newValue = Enum.Parse(headerTypeFieldType, "All");
var knownHeadersType = assemblyTypes.FirstOrDefault(n => n.Name == "KnownHeaders");
var contentTypeObj = knownHeadersType.GetFields().FirstOrDefault(n => n.Name == "ContentType").GetValue(null);
if (contentTypeObj is null) return;
headerTypeField.SetValue(contentTypeObj, newValue);
}
You can use this it will be work!
HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get,"URL");
msg.Content = new StringContent(string.Empty, Encoding.UTF8, "application/json");
HttpResponseMessage response = await _httpClient.SendAsync(msg);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
Ok, it's not HTTPClient but if u can use it, WebClient is quite easy:
using (var client = new System.Net.WebClient())
{
client.Headers.Add("Accept", "application/json");
client.Headers.Add("Content-Type", "application/json; charset=utf-8");
client.DownloadString(...);
}
try to use HttpClientFactory
services.AddSingleton<WebRequestXXX>()
.AddHttpClient<WebRequestXXX>("ClientX", config =>
{
config.BaseAddress = new System.Uri("https://jsonplaceholder.typicode.com");
config.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
config.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
});
======================
public class WebRequestXXXX
{
private readonly IHttpClientFactory _httpClientFactory;
public WebRequestXXXX(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public List<Posts> GetAllPosts()
{
using (var _client = _httpClientFactory.CreateClient("ClientX"))
{
var response = _client.GetAsync("/posts").Result;
if (response.IsSuccessStatusCode)
{
var itemString = response.Content.ReadAsStringAsync().Result;
var itemJson = System.Text.Json.JsonSerializer.Deserialize<List<Posts>>(itemString,
new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return itemJson;
}
else
{
return new List<Posts>();
}
}
}
}
I got the answer whith RestSharp:
private async Task<string> GetAccessTokenAsync()
{
var client = new RestClient(_baseURL);
var request = new RestRequest("auth/v1/login", Method.POST, DataFormat.Json);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("x-api-key", _apiKey);
request.AddHeader("Accept-Language", "br");
request.AddHeader("x-client-tenant", "1");
...
}
It worked for me.
You need to do it like this:
HttpContent httpContent = new StringContent(#"{ the json string }");
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage message = client.PostAsync(#"{url}", httpContent).Result;
For those wanting to set the Content-Type to Json specifically, you can use the extension method PostAsJsonAsync.
using System.Net.Http.Json; //this is needed for PostAsJsonAsync to work
//....
HttpClient client = new HttpClient();
HttpResponseMessage response = await
client.PostAsJsonAsync("http://example.com/" + "relativeAddress",
new
{
name = "John Doe",
age = 33
});
//Do what you need to do with your response
The advantage here is cleaner code and you get to avoid stringified json. More details can be found at: https://learn.microsoft.com/en-us/previous-versions/aspnet/hh944339(v=vs.118)
I find it most simple and easy to understand in the following way:
async Task SendPostRequest()
{
HttpClient client = new HttpClient();
var requestContent = new StringContent(<content>);
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync(<url>, requestContent);
var responseString = await response.Content.ReadAsStringAsync();
}
...
SendPostRequest().Wait();
I end up having similar issue.
So I discovered that the Software PostMan can generate code when clicking the "Code" button at upper/left corner. From that we can see what going on "under the hood" and the HTTP call is generated in many code language; curl command, C# RestShart, java, nodeJs, ...
That helped me a lot and instead of using .Net base HttpClient I ended up using RestSharp nuget package.
Hope that can help someone else!
Api returned
"Unsupported Media Type","status":415
Adding ContentType to the jsonstring did the magic and this is my script working 100% as of today
using (var client = new HttpClient())
{
var endpoint = "api/endpoint;
var userName = "xxxxxxxxxx";
var passwd = "xxxxxxxxxx";
var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
var authToken = Encoding.ASCII.GetBytes($"{userName}:{passwd}");
client.BaseAddress = new Uri("https://example.com/");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(authToken));
HttpResponseMessage response = await client.PostAsync(endpoint, content);
if (response.IsSuccessStatusCode)
{
// Get the URI of the created resource.
Uri returnUrl = response.Headers.Location;
Console.WriteLine(returnUrl);
}
string responseBody = await response.Content.ReadAsStringAsync();
return responseBody;
}
For my scenario, a third-party API was creating the HttpRequestMessage, so I was not able to use the top-voted answers to resolve the issue. And I didn't like the idea of messing with reflection so the other answers didn't work either.
Instead, I extended from AndroidMessageHandler and used this new class as a parameter to HttpClient. AndroidMessageHandler contains the method SendAsync which can be overridden in order to make changes to the HttpRequestMessage object before it is sent. If you don't have access to the Android Xamarin libaries, you may be able to figure something out with HttpMessageHandler.
public class XamarinHttpMessageHandler : global::Xamarin.Android.Net.AndroidMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Here I make check that I'm only modifying a specific request
// and not all of them.
if (request.RequestUri != null && request.RequestUri.AbsolutePath.EndsWith("download") && request.Content != null)
{
request.Content.Headers.Add("Content-Type", "text/plain");
}
return base.SendAsync(request, cancellationToken);
}
}
Then to use:
var client = new HttpClient(new XamarinHttpMessageHandler());
So if you're trying to do a /$batch OData request like this Microsoft article demonstrates where you're supposed to have a Content-Type header like:
Content-Type: multipart/mixed;boundary=batch_d3bcb804-ee77-4921-9a45-761f98d32029
string headerValue = "multipart/mixed;boundary=batch_d3bcb804-ee77-4921-9a45-761f98d32029";
//You need to set it like thus:
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(headerValue);
Again, the magic you need is: MediaTypeHeaderValue.Parse(...)
stringContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
And 🎉 YES! 🎉 ... that cleared up the problem with ATS REST API: SharedKey works now! 😄 👍 🍻
Source: https://github.com/dotnet/runtime/issues/17036#issuecomment-212046628

HttpClient calling API doesnt not pass cookie auth

Trying to call an API from a controller using HttpClient and the API does not recognize the user as authenticated and logged in. When calling the API from JS I have no issue. I noticed the HttpClient was only sending via HTTP 1.1 and so I upgraded to 2.0 settings the DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER flag but this made no difference. I have tried all combinations of the HttpClientHandler properties including UseCookies and the request is never authenticated.
using (var handler = new HttpClientHandler {UseDefaultCredentials = true})
{
using (var httpClient = new HttpClient(handler))
{
var response = httpClient.GetStringAsync(new Uri($"https://localhost:64366/api/")).Result;
}
}
Will move to token based auth in the future but for now would like to understand why there is a difference between calling the API from C# vs JS. This is all HTTPS on localhost using asp net core 2.2.
Difference between JS and C# is that browsers attach cookies automatically to requests and you have to attach cookies manually in C# as juunas mentioned.
To obtain and use authentication cookie you may use the following pattern
CookieContainer cookies = new CookieContainer(); //this container saves cookies from responses and send them in requests
var handler = new HttpClientHandler
{
CookieContainer = cookies
};
var client = new HttpClient(handler);
string authUrl = ""; //your auth url
string anyUrl = ""; //any url that requires you to be authenticated
var authContent = new FormUrlEncodedContent(
new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("login", "log_in"),
new KeyValuePair<string, string>("password", "pass_word")
}
);
//cookies will be set on this request
HttpResponseMessage auth = await client.PostAsync(authUrl, authContent);
auth.EnsureSuccessStatusCode(); //retrieving result is not required but you will know if something goes wrong on authentication
//and here retrieved cookies will be used
string result = await client.GetStringAsync(anyUrl);

ASP.NET Core Web API calling other Web API using Client Certificate getting 401

I have been trying to add a client certificate to the HttpClient in a .NET Core 1.1 WebAPI for the past 20 minutes and can not figure out what the issue is. When I debug the program and check to see if the certificate was attached with the handler, I am able to see the certificate. I still receive a 401 though. I know for certain that the certificate is properly installed on the other API as I am able to make calls using it from another program. The other program is using .NET Framework 4.5.2, but I doubt that would be the issue the code is almost identical. The only difference is I need to use HttpClientHandlerfor the .NET Core instead of WebRequestHandler.
.NET Core 1.1
string uri = "https://other-api-url.com/something";
try
{
X509Certificate2 clientCert = GetClientCertificate();
HttpClientHandler client = new HttpClientHandler();
requestHandler.ClientCertificates.Add(clientCert);
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri(uri)
};
HttpResponseMessage response = client.GetAsync("").Result;
response.EnsureSuccessStatusCode();
string responseContent = response.Content.ReadAsStringAsync().Result;
return Ok(responseContent);
}
catch (Exception ex)
{
return BadRequest(ex.Message + uri);
}
.NET Framework 4.5.2 Code:
string uri = "https://other-api-url.com/something";
try
{
X509Certificate2 clientCert = GetClientCertificate();
WebRequestHandler requestHandler = new WebRequestHandler();
requestHandler.ClientCertificates.Add(clientCert);
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri(uri)
};
HttpResponseMessage response = client.GetAsync("").Result;
response.EnsureSuccessStatusCode();
string responseContent = response.Content.ReadAsStringAsync().Result;
return Ok(responseContent);
}
catch (Exception ex)
{
return BadRequest(ex.Message + uri);
}
It is an older post, but I had the same scenario using certificates.
Make sure the UseDefaultCredentials property on the HttpClientHandler is set to true. Because it is false by default.
See quote below from the HttpClientHandler class:
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.usedefaultcredentials?view=net-5.0#System_Net_Http_HttpClientHandler_UseDefaultCredentials
Set this property to true when requests made by the HttpClientHandler
object should, if requested by the server, be authenticated using the
credentials of the currently logged on user. For client applications,
this is the desired behavior in most scenarios. For middle-tier
applications, such as ASP.NET applications, instead of using this
property, you would typically set the Credentials property to the
credentials of the client on whose behalf the request is made.

Categories