Authenticating HTTP requests using cookies from an embedded browser in a desktop application can be useful for automating tasks on websites protected by Cloudflare Turnstile. This article demonstrates how to extract cookies from a WebView2
control and add them to the headers of an HttpRequestMessage
in a WPF application.
To achieve this, we need to create a DelegatingHandler
that holds a reference to our WebView2
control:
internal sealed class WebViewSetCookieHandler(WebView2 webView, HttpMessageHandler innerHandler) : DelegatingHandler(innerHandler)
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri != null)
{
var cookieHeader = await this.GetCookieHeaderAsync(request.RequestUri).ConfigureAwait(false);
if (!string.IsNullOrEmpty(cookieHeader))
{
request.Headers.Add("Cookie", cookieHeader);
}
}
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
private async Task<string> GetCookieHeaderAsync(Uri uri)
{
CookieContainer cookieContainer;
if (webView.CheckAccess())
{
cookieContainer = await this.GetCookieContainerAsync(uri).ConfigureAwait(false);
}
else
{
var cookieContainerTask = webView.Dispatcher.InvokeAsync(() => this.GetCookieContainerAsync(uri)).Task.Unwrap();
cookieContainer = await cookieContainerTask.ConfigureAwait(false);
}
return cookieContainer.GetCookieHeader(uri);
}
private async Task<CookieContainer> GetCookieContainerAsync(Uri uri)
{
var webViewCookies = await webView.CoreWebView2.CookieManager.GetCookiesAsync(uri.ToString()).ConfigureAwait(false);
var cookieContainer = new CookieContainer();
foreach (var cookie in webViewCookies)
{
cookieContainer.Add(cookie.ToSystemNetCookie());
}
return cookieContainer;
}
}
When an HTTP request is sent, we ask the WebView2
control’s cookie manager to return the cookies for the request’s URL. These cookies are then converted into System.Net.Cookie
objects and stored in a CookieContainer
, which takes care of serializing them into a string for the request headers.
It is important to consider that the code may execute on a thread different from the UI thread. This is why we need to check whether the dispatcher is available for the current thread and use InvokeAsync
to run the GetCookieContainerAsync
method on the UI thread when necessary.
This handler can be used within a custom HTTP client once the WebView2
control has been initialized:
internal sealed partial class MainWindow
{
private readonly HttpClient _httpClient;
public MainWindow()
{
this.InitializeComponent();
var setCookieHandler = new WebViewSetCookieHandler(this.MyWebView, HttpClientDefaults.PrimaryHandler);
this._httpClient = new HttpClient(setCookieHandler);
}
}
internal static class HttpClientDefaults
{
public static readonly SocketsHttpHandler PrimaryHandler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
};
}
The
CoreWebView2
property ofWebView2
is undefined until theEnsureCoreWebView2Async
method has been called on the control. A good place to do this is by overriding theOnContentRendered
method of the parent control.
Photo by Luis Quintero