136

I've got the following code that works successfully. I can't figure out how to get the cookie out of the response. My goal is that I want to be able to set cookies in the request and get cookies out of the response. Thoughts?

private async Task<string> Login(string username, string password)
{
    try
    {
        string url = "http://app.agelessemail.com/account/login/";
        Uri address = new Uri(url);
        var postData = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("username", username),
            new KeyValuePair<string, string>("password ", password)
        };

        HttpContent content = new FormUrlEncodedContent(postData);
        var cookieJar = new CookieContainer();
        var handler = new HttpClientHandler
        {
            CookieContainer = cookieJar,
            UseCookies = true,
            UseDefaultCredentials = false
        };

        var client = new HttpClient(handler)
        {
            BaseAddress = address
        };


        HttpResponseMessage response = await client.PostAsync(url,content);
        response.EnsureSuccessStatusCode();
        string body = await response.Content.ReadAsStringAsync();
        return body;
    }
    catch (Exception e)
    {
        return e.ToString();
    }
}

Here is the complete answer:

HttpResponseMessage response = await client.PostAsync(url,content);
response.EnsureSuccessStatusCode();

Uri uri = new Uri(UrlBase);
var responseCookies = cookieJar.GetCookies(uri);
foreach (Cookie cookie in responseCookies)
{
    string cookieName = cookie.Name;
    string cookieValue = cookie.Value;
}
PoLáKoSz
  • 355
  • 1
  • 6
  • 7
Peter Kellner
  • 14,748
  • 25
  • 102
  • 188
  • Out of curiosity, can I ask why you want to read cookies on the client? My understanding is that cookies are used for sending information to the server, not for returning information. – Darrel Miller Nov 11 '12 at 02:47
  • I use the returned cookie on calls that return JSON so that I don't have to do a separate authorization call for each JSON call. That is to say, I have a call log /Home/GetData which returns JSON but only if authorized. On the client request, I add the cookie so that /Home/GetData will respond. Otherwise it will say "403" unauthorized. – Peter Kellner Nov 11 '12 at 13:13
  • Setting the authorization header as a default header is almost as effective and a bit more standard. There is just no way for the server to set the auth header automatically on behalf of the client. – Darrel Miller Nov 14 '12 at 20:00
  • 1
    thanks for the tip Darrel. Do you have any examples of what that might look like in asp.net? I struggled with this for my monotouch and now my windows store app. I'd be happy if there was a simple way. This is a pain, especially with async and await now on windows store apps. – Peter Kellner Nov 14 '12 at 23:19

4 Answers4

215

To add cookies to a request, populate the cookie container before the request with CookieContainer.Add(uri, cookie). After the request is made the cookie container will automatically be populated with all the cookies from the response. You can then call GetCookies() to retreive them.

CookieContainer cookies = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookies;

HttpClient client = new HttpClient(handler);
HttpResponseMessage response = client.GetAsync("http://google.com").Result;

Uri uri = new Uri("http://google.com");
IEnumerable<Cookie> responseCookies = cookies.GetCookies(uri).Cast<Cookie>();
foreach (Cookie cookie in responseCookies)
    Console.WriteLine(cookie.Name + ": " + cookie.Value);

Console.ReadLine();
Despertar
  • 21,627
  • 11
  • 81
  • 79
  • 12
    Note: After receiving the initial cookies in the first call, when accessing any pages from the same domain, the cookies will be sent automatically, no additional steps needed. – James John McGuire 'Jahmic' Aug 17 '17 at 07:57
  • 1
    You must add "using System.linq;" – Cabuxa.Mapache May 07 '18 at 06:24
  • ^^ System.Linq is only required if you wish to utilize the .Cast<> method, it's not required for the cookie retrieval, but this should be self explanatory :) – MikeDub Jan 09 '19 at 02:55
  • 2
    Is it possible to get cookies the same way if `httpclient` is built from the httpclient factory ? i.e builder method added to services.AddHttpClient – user3279954 Aug 05 '19 at 22:15
  • 1
    on get Async we already made a reference to "google.com", why do we need to declare a URI with a reference to it again? – Malcolm Salvador Nov 13 '19 at 01:52
  • very nice solution and works well for the scenario where serverside is making a request that has a Set-Cookies header in response – Kyle Huang Apr 03 '20 at 00:34
  • @MikeDub Why would we use the cast in the first place if it is already in the correct form? – FireController1847 Apr 17 '21 at 22:15
  • 1
    @FireController1847 you only need to cast if you want to use it as a IEnumerable, as the GetCookies() method returns a CookieCollection, not IEnumerable. IMO the cast is not required if all you are doing is using the loop in the code above as you can still Enumerate over a CookieCollection due to it inheriting IEnumerable. – MikeDub Apr 29 '21 at 03:36
  • This solution works in .net core as well. Thanks! – Michael Jun 23 '22 at 20:20
21

There's alternative if you don't have access to the HttpClient and can't inject the CookieContainer. This works in .NET Core 2.2:

private string GetCookie(HttpResponseMessage message)
{
    message.Headers.TryGetValues("Set-Cookie", out var setCookie);
    var setCookieString = setCookie.Single();
    var cookieTokens = setCookieString.Split(';');
    var firstCookie = cookieTokens.FirstOrDefault();
    var keyValueTokens = firstCookie.Split('=');
    var valueString = keyValueTokens[1];
    var cookieValue = HttpUtility.UrlDecode(valueString);
    return cookieValue;
}
Rytis I
  • 1,105
  • 8
  • 19
  • 2
    Just tried this, the `var setCookieString = setCookie.Single();` should probably be changed as it is quite possible for `setCookie` to have multiple values and thus cause an exception to be thrown. I ended up adding a parameter `string cookieName` and then used `setCookie.Single(x => x.StartsWith(cookieName));`. This does still assume the requested cookie exists. – The Thirsty Ape Feb 24 '22 at 23:03
  • `Set-Cookie` header does have multiple values, they are separated by a semicolon(`;`), but normally there should be only a single `Set-Cookie` header – Rytis I Apr 20 '22 at 08:21
8

You can easily get a cookie value with the given URL.

private async Task<string> GetCookieValue(string url, string cookieName)
{
    var cookieContainer = new CookieContainer();
    var uri = new Uri(url);
    using (var httpClientHandler = new HttpClientHandler
    {
        CookieContainer = cookieContainer
    })
    {
        using (var httpClient = new HttpClient(httpClientHandler))
        {
            await httpClient.GetAsync(uri);
            var cookie = cookieContainer.GetCookies(uri).Cast<Cookie>().FirstOrDefault(x => x.Name == cookieName);
            return cookie?.Value;
        }
    }
}
Alper Ebicoglu
  • 8,884
  • 1
  • 49
  • 55
2

Not in every case you can add httpClientHandler to httpClient. For example, when you use integration tests testServer.CreateClient() or inject httpClient from IHttpClientFactory. So, I have simply read values from header.

    public static List<Cookie> GetCookies(this HttpResponseMessage message)
    {
        message.Headers.TryGetValues("Set-Cookie", out var cookiesHeader);
        var cookies = cookiesHeader.Select(cookieString => CreateCookie(cookieString)).ToList();
        return cookies;
    }

    private static Cookie CreateCookie(string cookieString)
    {
        var properties = cookieString.Split(';', StringSplitOptions.TrimEntries);
        var name = properties[0].Split("=")[0];
        var value = properties[0].Split("=")[1];
        var path = properties[2].Replace("path=", "");
        var cookie = new Cookie(name, value, path)
        {
            Secure = properties.Contains("secure"),
            HttpOnly = properties.Contains("httponly"),
            Expires = DateTime.Parse(properties[1].Replace("expires=", ""))
        };
        return cookie;
    }

CreateCookie method may be modified to exactly match your cookie properties.

Deivydas Voroneckis
  • 1,973
  • 3
  • 19
  • 40