February 12th, 2024

How can I get the Windows Runtime HttpClient to display a basic authentication prompt?

A customer was trying to get the Windows Runtime Windows.Web.Http.HttpClient to display a basic authentication prompt when used from a classic Win32 application. Unfortunately, no authentication dialog appeared even though they set AllowUI = true; the request reported status 401 (Unauthorized).

namespace winrt
{
    using namespace winrt::Windows::Foundation;
    using namespace winrt::Windows::Web::Http;
    using namespace winrt::Windows::Web::Http::Filters;
}

winrt::IAsyncOperation<winrt::HttpStatusCode> Sample(HWND hwnd)
{
    auto uri = winrt::Uri(L"http://httpbin.org/basic-auth/user/password");
    auto filter = winrt::HttpBaseProtocolFilter();
    filter.AllowUI(true);

    auto client = winrt::HttpClient(filter);
    auto result = co_await client.GetAsync(uri);

    co_return result.StatusCode();
}

Originally, the objects in the Windows Runtime namespace were intended for use in UWP apps (Universal Windows Platform). In UWP apps, each thread can have at most one CoreWindow object, and it was common for Windows Runtime objects to infer their UI context by asking the thread for its CoreWindow, and then using that CoreWindow for any further user interface operations.¹

But classic Win32 apps don’t follow the “one CoreWindow per thread” model. They can create multiple windows on each thread, and none of them are required to be a CoreWindow. Under these conditions, Windows Runtime objects that were designed for UWP apps run into a problem: What window should they use for user interface operations?

When used in a classic Win32 app, the Http­Base­Protocol­Filter tries to guess which window to use: Sometimes it guesses correctly, sometimes it guesses wrong, and sometimes its guess comes up empty.

To avoid guessing, use the IInitialize­With­Window interface (which we learned about earlier) to give the Http­Base­Protocol­Filter an explicit window to use.

namespace winrt
{
    using namespace winrt::Windows::Foundation;
    using namespace winrt::Windows::Web::Http;
    using namespace winrt::Windows::Web::Http::Filters;
}

winrt::IAsyncOperation<winrt::HttpStatusCode> Sample(HWND hwnd)
{
    auto uri = winrt::Uri(L"http://httpbin.org/basic-auth/user/password");
    auto filter = winrt::HttpBaseProtocolFilter();
    filter.as<IInitializeWithWindow>()->Initialize(hwnd);
    filter.AllowUI(true);

    auto client = winrt::HttpClient(filter);
    auto result = co_await client.GetAsync(uri);

    co_return result.StatusCode();
}

With this extra nudge, the Http­Base­Protocol­Filter will be able to display the basic authentication dialog.

¹ This model stopped working even for UWP apps with the introduction of AppWindow objects, which let you create multiple windows on a single thread.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Joshua Hudson

    I am immediately left wondering if `IInitializeWindow::Initialize` can be called with the result of `CreateDesktopWindow()`.