In order to work around cases where it wasn't possible automatically authenticate with a website, I wanted the ability to use an embedded Internet Explorer window for manual authentication and then reuse the cookies. This article describes how to read cookies indirectly using InternetGetCookieEx or directly from a WebBrowser control.

Reading cookies via InternetGetCookieEx
Reading cookies via InternetGetCookieEx

Reading cookies

The InternetGetCookieEx Win32 API can be used to read cookie name value pairs for a given URI. You can use it to read all cookies or a named cookie. You can also specify which type of cookies to include.

csharp
BOOLAPI InternetGetCookieExW(
  LPCWSTR lpszUrl,
  LPCWSTR lpszCookieName,
  LPWSTR  lpszCookieData,
  LPDWORD lpdwSize,
  DWORD   dwFlags,
  LPVOID  lpReserved
);

To use this function, we specify the URL we want to query, the name of the cookie to lookup (or null for all), a buffer to store the cookie data, the length of the buffer and then flags to describe what we want returned. The function returns true if it succeeds, otherwise false. In line with other Win32 calls, you can use GetLastError to get the error code in the event of a failure.

The last part is quite important as if the buffer provided is too small, the function will fail and calling GetLastError will return ERROR_INSUFFICIENT_BUFFER. In this case, the buffer length parameter will contain the size required to contain the data so that you can allocate a new buffer and call the function again.

I saw a number of naïve implementations on the internet that called InternetGetCookieEx without doing any sort of error checking; this could lead to subtle bugs in the event that the cookies are longer than the supplied buffer as no data will be returned.

The following function can be used to get all cookies for a given URI. I create a buffer that will hold 1024 characters and make a call to InternetGetCookieEx. If it fails but the error code is ERROR_INSUFFICIENT_BUFFER then I increase the buffer size and try again.

csharp
[DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool InternetGetCookieEx(string lpszUrl, string lpszCookieName, StringBuilder lpszCookieData, ref int lpdwSize, int dwFlags, IntPtr lpReserved);

const int ERROR_INSUFFICIENT_BUFFER = 122;

const int INTERNET_COOKIE_HTTPONLY = 0x00002000;

public static string GetCookies(string uri)
{
  StringBuilder buffer;
  string result;
  int bufferLength;
  int flags;

  bufferLength = 1024;
  buffer = new StringBuilder(bufferLength);

  flags = INTERNET_COOKIE_HTTPONLY;

  if (InternetGetCookieEx(uri, null, buffer, ref bufferLength, flags, IntPtr.Zero))
  {
    result = buffer.ToString();
  }
  else
  {
    result = null;

    if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
    {
      buffer.Length = bufferLength;

      if (NativeMethods.InternetGetCookieEx(uri, null, buffer, ref bufferLength, flags, IntPtr.Zero))
      {
        result = buffer.ToString();
      }
    }
  }

  return result;
}

In the example above, I'm requesting to include HTTP only cookies. The documentation states: "Do not use this flag if you expose a scriptable interface, because this has security implications. It is imperative that you use this flag only if you can guarantee that you will never expose the cookie to third-party code by way of an extensibility mechanism you provide."

According to the documentation, the lpdwSize parameter will be updated to specify the number of bytes required to hold the value, however, I'm creating a buffer that uses that value in chars to keep it simple.

Using the WebBrowser control directly

Reading cookies using both InternetGetCookieEx and an embedded WebBrowser control
Reading cookies using both InternetGetCookieEx and an embedded WebBrowser control

While using InternetGetCookieEx would be my preferred approach, if you already have a WebBrowser control you can read the cookies directly from the control. The Document property of a WebBrowser control returns a HtmlDocument that in turn contains the Cookie property. The format of the returned data matches that of InternetGetCookieEx.

There is one important difference between using InternetGetCookieEx and HtmlDocument.Cookie - the later does not include HTTP only cookies. If you require access to all cookies for a given URI, then use InternetGetCookieEx instead.

The methods describe above only seem to return the names and values of cookies, excluding properties such as paths and expiry dates. Unfortunately I don't know of a method of accessing these extended properties in modern versions of Windows.

Using the cookies

As Windows and browsers have evolved, there is probably little use for this particular API call. Internet Explorer is obsolete and third party browsers (and Edge) use their own cookie storage mechanisms that this API cannot access. Speaking for myself, the only use I have for this technique is to make custom requests to an internet resource after using an embedded IE window to authenticate with a website.

The following example demonstrates how you could assign cookies to a HttpWebRequest. Note that the SetCookies method of the CookieContainer class requires the cookies to be comma separated, rather than the semi-colon seperated values returned by the above function.

csharp
CookieContainer cookies;
Uri uri;
string cookieData;

uri = new Uri("https://demo.cyotek.com/features/cookies.php");

cookieData = GetCookies(uri);

cookies = new CookieContainer();
cookies.SetCookies(uri, cookieData.Replace("; ", ","));

HttpWebRequest request;

request = WebRequest.CreateHttp(uri);
request.CookieContainer = cookies;

Demonstration program

The attached demonstration program includes a complete sample for reading cookies either via InternetGetCookieEx or from a WebBrowser control, and is also available from our GitHub page.

Postscript

I originally wrote this demonstration back in October 2018 but I delayed it while trying to find out a solution to the missing properties. I was also curious as to why Edge didn't seem to use the same system and had a vague idea looking into that. Then came the news that Microsoft have decided to drop their custom Edge engine and use Chromium instead.

This seems astonishingly short sighted to me and is a real disappointment.

While I confess I don't use Edge as my primary browser (Firefox is my daily driver due to its extension support and excellent developer tools), I found it to be a decent browser which didn't let me down when I did use it. To me it is perplexing that they are essentially ceding control to Google. It's like Windows Phone all over again and I like my Windows Phone. It is also frustrating as currently you can get a reasonable browsing experience by embedding Internet Explorer via the WebBrowser control into your application (with a little help) without having to worry about shipping any dependencies and I had hopes of doing the same with Edge for a truly modern experience.

Update History

  • 2019-01-20 - First published
  • 2020-11-22 - Updated formatting

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal


Files


Comments

# Chetan

How to do same with Microsoft Edge browser instead of IE?

Reply