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
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
How to Iterate Through the Following Json Using C#
C# How to Check If a Url Exists/Is Valid
How to Remove Empty Lines from a Formatted String
Clearing a Textbox Leaves an Invisible Character
Using Linq to Groupby and Sum Datatable
Convert Time-Formatted Column in Excel to C# Datetime
Using Linq for Multiple Condition in Where
Repaired Records:Cell Information from Worksheet Created from Scratch
C# SQL Server Stored Procedure Parameter Return List
How to Check If a Datetime Value Is Empty or Not in a Put Request
Read and Parse a Json File in C#
Asp.Net Core Form Post Results in a Http 415 Unsupported Media Type Response
Convert a List of Objects from One Type to Another Using Lambda Expression
Save Byte[] into a SQL Server Database from C#
Redirecting to Another Page in ASP.NET MVC Using Javascript/Jquery