Cookie Sharing with WebView and the Http Client

Despite their age, cookies remain just as relevant in today’s web as they were when originally introduced. The WebView will handle cookies similar to a regular browser. When making web calls via the native http client, this service also reads and stores cookies as you load webpages that contain them. Thankfully, UWP and iOS share their cookie containers automatically between the WebView and native http client, however Android does not.

One of the common cookie sharing scenario’s I have seen, is loading a login page in the WebView. Then with the http client, send a POST or GET to the webpage after login has occurred, with the credentials in a cookie.

iOS

In iOS if you are using NSUrlSession, your cookies are shared automatically with the UIWebView/WKWebView and NSUrlSession.

To access the Cookie Container, you just need to call upon NSHttpCookieStorage.SharedStorage.Cookies.

Deleting cookies is fairly easy, by looping through all cookies and calling delete for each one.

public void ClearCookies()
{
    foreach (var c in NSHttpCookieStorage.SharedStorage.Cookies)
        NSHttpCookieStorage.SharedStorage.DeleteCookie(c);
}

Android

Android unfortunately, doesn’t have any nice cookie sharing abilities, in its native platform http client. Hence you can still use the native http client or use the System.Net.Http.HttpClient. Android also has its own HttpClient, but for this example we will use the Mono one.

If you are using System.Net.Http.HttpClient, you will need to manually create your Cookie Container to ensure you can access it at a later time.

CookieContainer _cookieContainer = new CookieContainer();
System.Net.Http.HttpClient _client = new System.Net.Http.HttpClient(new HttpClientHandler() { CookieContainer = _cookieContainer });

The WebView in Android, will use the Android.WebKit.CookieManager to handle all of its cookies.

CookieManager.Instance;

If you want to share cookies between the two, you will have to manually process the information. You need to know the Uri of the domain for these cookies.

private void CopyCookies(HttpResponseMessage result, Uri uri)
{
    foreach (var header in result.Headers)
        if (header.Key.ToLower() == "set-cookie")
            foreach (var value in header.Value)
                 _cookieManager.SetCookie($"{uri.Scheme}://{uri.Host}", value);

    foreach (var cookie in GetAllCookies(_cookieContainer))
        _cookieManager.SetCookie(cookie.Domain, cookie.ToString());
}
public void ReverseCopyCookies(Uri uri)
{
    var cookie = _cookieManager.GetCookie($"{uri.Scheme}://{uri.Host}");

    if (!string.IsNullOrEmpty(cookie))
        _cookieContainer.SetCookies(new Uri($"{uri.Scheme}://{uri.Host}"), cookie);
}

If you want to get all the cookies in Android, there is a little, completely breakable in future versions, reflection approach. Use this with caution. Side note: I use Polly here because there is no lock, or way to lock an internal reference, we may need to retry if possible.

private IEnumerable<Cookie> GetAllCookies(CookieContainer cookieContainer)
{
    Hashtable domains = (Hashtable)cookieContainer.GetType().GetField("m_domainTable", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(cookieContainer);

    IList<Cookie> cookies = new List<Cookie>();

    var policy = Policy.Handle<InvalidOperationException>()
                       .WaitAndRetry(new[] { TimeSpan.FromSeconds(1),
                                             TimeSpan.FromSeconds(1),
                                             TimeSpan.FromSeconds(1) });

    // Possibility that the container changed while looping. Since we can't lock the internal field, a simple retry is the only solution.
    return policy.Execute(() =>
    {
        foreach (DictionaryEntry element in domains)
        {
            SortedList list = (SortedList)element.Value.GetType().GetField("m_list", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(element.Value);
            foreach (var element in list)
            {
                var collection = (CookieCollection)((DictionaryEntry)element).Value;
                foreach (Cookie cookie in collection)
                    cookies.Add(cookie);
            }
        }
        return cookies;
    });
}

UWP

In Windows, if you are using Windows.Web.Http.HttpClient, you can access your cookie container by ensuring you supply the Protocol Filter.

private readonly HttpBaseProtocolFilter _filter = new HttpBaseProtocolFilter();
private Windows.Web.Http.HttpClient _httpClient = new Windows.Web.Http.HttpClient(_filter);

By doing this you can access the _filter.CookieManager and from here you can view, add, modify or delete cookies.

Unfortunately the only downside to the UWP CookieManager, is the inability to delete all cookies in one command, or even get all cookies. You must know the domain of the cookie’s you want to delete and get all cookies from that domain, while deleting one by one.

public async Task ClearCookies()
{
    var domains = new List<Uri>();
    // List domains here, or maybe pass as parameter to this function.
    foreach (var domain in domains)
        foreach (var cookie in _filter.CookieManager.GetCookies(domain))
            _filter.CookieManager.DeleteCookie(cookie);
}

Summary

iOS and UWP nicely share cookies with the native http client, however Android requires manual intervention. If you wish to use the Mono Http Client, you will want to use similar Android methods, to share cookies between containers, however the management can become time consuming, and prone to error.

Microsoft MVP | Xamarin MVP | Xamarin Certified Developer |
Exrin MVVM Framework | Xamarin Forms Developer | Melbourne, Australia

Related Posts

Leave A Comment?