How to Set a Cookie on Httpclient'S Httprequestmessage

How do I set a cookie on HttpClient's HttpRequestMessage

Here's how you could set a custom cookie value for the request:

var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("foo", "bar"),
new KeyValuePair<string, string>("baz", "bazinga"),
});
cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
var result = await client.PostAsync("/test", content);
result.EnsureSuccessStatusCode();
}

HttpRequestMessage doesn't add Cookie into request

You shouldn't add cookies with simply adding a header because there are things that CookieContainer and HttpCookie will handle for you, things such as Expiration, Path, Domain and correct way of setting name and values for cookies.

The better way is to use CookieContainer.

var baseAddress = new Uri('http://localhost');

HttpRequestMessage requestMessage = new HttpRequestMessage { Method = method };
requestMessage.Headers.Add("custom1", "c1");
requestMessage.Headers.Add("custom2", "c2");
// requestMessage.Headers.Add("Cookie", "c3"); wrong way to do it

var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using(HttpClient client = new HttpClient(handler) { BaseAddress = baseAddress })
{
cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
using (var response = await client.SendAsync(requestMessage, cancellationToken))
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
// do your stuff
}
}
}

Off-topic recommendation:

DO NOT create a new instance of HttpClient every time. It will cause all sockets get busy. Please follow better approaches like singleton or HttpClientFactory.

HttpClient is sending request cookies from other requests' responses

It turns out that, by default, the HttpClientHandler will store the response cookies in a CookieContainer and then append them onto the next request.

This explains why I was seeing extra cookies on the remote API's requests, but they were actually coming from the response of a previously completed request.

This documentation led me to the fix

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#cookies-1

ASP.NET Core documentation: HttpClient & Cookies

So by adding this code:

services.AddHttpClient<IRemoteService, RemoteService>()
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
})
.AddHttpMessageHandler<CopyCookieHandler>();

will prevent the HttpClientHandler from sharing your cookies between request on your HttpClient.

Can't get any cookies with C# HttpClient

I tried to write the response headers to the console with Console.WriteLine(response.Headers) and a Set-Cookie header with the csrf token was printed to the console. So it seems that HttpClient doesn’t count cookies in this header as actual cookies, thus not adding these said cookies to the CookieContainer.

C# How to pass on a cookie using a shared HttpClient

Instead of calling PutAsJsonAsync() you can use HttpRequestMessage and SendAsync():

Uri requestUri = ...;
HttpMethod method = HttpMethod.Get /*Put, Post, Delete, etc.*/;
var request = new HttpRequestMessage(method, requestUri);
request.Headers.TryAddWithoutValidation("Cookie", ".ASPXAUTH=" + authCookieValue);
request.Content = new StringContent(jsonDataToSend, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);

UPDATE:
To make sure that your HTTP client does not store any cookies from a response you need to do this:

var httpClient = new HttpClient(new HttpClientHandler() { UseCookies = false; });

Otherwise you might get unexpected behavior by using one client and sharing other cookies.

HttpClient does not see "Set-Cookie" header

Figured it out - MVC action send 2 responses back one with cookie and the second one without cookie. HttpClient shows latest response that is why it could not find "Set Cookie" header.

But!

It seems like HttpClient save cookie from the first response internally and on the request to the secured page under the same domain HttpClient automatically applies that saved cookie.

In my example if I'll connect to "localhost:4391/api/Person/1" with HttpClient I'll get unauthorized. So my goal was to go to "localhost:4391/Account/Login" with HttpClient, log in, get generated cookie then go to "localhost:4391/api/Person/1" and send that cookie but as I mentioned before HttpClient does that automatically and I dont need to extract cookie from the first request to Login page.

So code below works as needed!

private static void GetPerson()
{
Console.WriteLine("Username:");
string username = Console.ReadLine();

Console.WriteLine("Password:");
string password = Console.ReadLine();

HttpClient httpClient = new HttpClient();

HttpRequestMessage authRequest = new HttpRequestMessage();
authRequest.Method = HttpMethod.Post;
authRequest.RequestUri = new Uri(@"http://localhost:4391/Account/Login");
authRequest.Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Username", username),
new KeyValuePair<string, string>("Password", password)
});

HttpResponseMessage authResponse = httpClient.SendAsync(authRequest).Result;

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, @"http://localhost:4391/api/Person/1");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

HttpResponseMessage response = httpClient.SendAsync(request).Result;

if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Username or password is incorrect");
return;
}

response.Content.ReadAsAsync<Person>().ContinueWith((x) => {
Person person = x.Result;

Console.WriteLine("First name: {0}", person.FirstName);
Console.WriteLine("Last name: {0}", person.LastName);
});
}

But going back to catching that cookie I would have been able to accomplish that if my action in the controller looked like this:

[HttpPost]
public ActionResult Login(string username, string password)
{
if (username == password)
{
FormsAuthentication.SetAuthCookie(username, true);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
return new HttpUnauthorizedResult();
}

How to add a cookie to a request using HttpClient from IHttpClientFactory

The solution was indeed to use header propagation. I just had to get all the pieces together correctly.

Header propagation is configured inside ConfigureServices (in Startup.cs). And since I want to use data from the current user's claims, the trick is, when adding the cookie header, to use on of the overloads that takes in a function that gives you access to the current context:

services.AddHeaderPropagation(options =>
{
options.Headers.Add("Cookie", context =>
{
var accessToken = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "access-token")?.Value;
return accessToken != null ? new StringValues($"token={accessToken}") : new StringValues();
});
});

In addition to this, since I'm using a Typed Service, I also had to configure that that specific service should forward the cookie header (at the same place inside ConfigureServices in Startup.cs):

services.AddHttpClient<IApiService, ApiService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://httpbin.org/");
}).AddHeaderPropagation(options =>
{
options.Headers.Add("Cookie");
});

And finally, the thing that caused me some trouble for a little while: Since I'm using data from the current users claims, the registration of the header propagation middleware (app.UseHeaderPropagation(); inside Configure in Startup.cs) must happen after adding the authentication middleware (app.UseAuthentication();). If not, the claims haven't been set yet.

And as a final tip: I head great us in https://httpbin.org/ when working on this. The request inspection endpoints there are really useful to see what data you actually pass along in your request.



Related Topics



Leave a reply



Submit