Null Parent Window Handler Issue for AzureAuth Library

Background

AzureAuth-Lib is a thin wrapper on top of microsoft-authentication-cli, which is a CLI wrapper of MSAL. These CLI tools are heavily used by developers.

Starting from September 2024, users reported an issue with the tool, stating that AzureAuth-Lib failed to acquire an access token due to a null reference exception. Here’s the error stack:

[Verbose] MSAL.Desktop.4.54.1.0.MsalClientException: ErrorCode: window_handle_required
  Microsoft.Identity.Client.MsalClientException: A window handle must be configured.
  See https://aka.ms/msal-net-wam#parent-window-handles

Error Code Deep-Dive

But what does window_handle_required mean?

After looking into the link in the error stack, I learned that the identity SDK team released a new version of the MSAL library to improve the experience for developers using authentication brokers to simplify the token acquisition process. One of the requirements is that a parent window must be configured for the broker mode to work.

When an authentication window pops up, we don’t want it to appear randomly, as it could be hidden by other windows or displayed in an inconvenient location on the screen. Developers should specify the window they want to bind via the WithParentActivityOrWindow method.

You can find more details in this blog: Improved Windows Broker Support with MSAL.NET

Native Windows API Library

What confuses me is that AzureAuth has already used WithParentActivityOrWindow() to bind to a parent window. See this line:

var clientBuilder =
    PublicClientApplicationBuilder
    .Create($"{clientId}")
    .WithAuthority($"https://login.microsoftonline.com/{tenantId}")
    .WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
    {
        Title = this.promptHint,
    })
    .WithParentActivityOrWindow(() => this.GetParentWindowHandle());

We leveraged the native Windows API as follows:

[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();

This code declares a dependency on an external method implemented in the kernel32.dll library. The return type IntPtr is a platform-specific type used to represent a pointer or a handle. This method allows us to get a handle that points to the console window.

This means we already implemented the necessary functionality. In fact, when we run azureauth.exe in Terminal or PowerShell, the authentication window pops up without any issue.

So what’s wrong here? Why does azureauth.exe work in a regular Terminal while azureauth-lib, a thin wrapper of the exe file, doesn’t work

Call Hierarchy

To answer this question, we need to under the call hierarchy in the main function of azureauth-lib:

azureauth-lib
|- medallion shell
    |- azureauth.exe
        |- GetConsoleWindow

It appears that in the context of Medallion Shell, azureauth.exe failed to obtain a handler.

One potential solution is to create an empty console window using the native Windows API.

I found some relevant documents online:

However, I was unable to create one due to my limited knowledge of native Windows APIs. Additionally, creating an empty window to avoid the null pointer exception seems like a hacky solution.

Therefore, I shifted my focus to the Medallion Shell.

Medallion Shell

Medallion Shell is a wrapper of the built-in System.Diagnostics.Process class in .NET. Running a process is as simple as:

var command = Command.Run(executable, args);
await command.Task.ConfigureAwait(false);

I discussed the issue with the author of the library in issue 121, and initially, we suspected there might be a problem with sending signals from Medallion Shell.

There is a Signals folder, but GetConsoleWindow() is not part of it. I then forked the main branch and added a reference to the extern library there.

After building Medallion Shell locally, I added it to my test project with the following steps:

  1. right-click Dependencies
  2. select Add Project Reference...
  3. browse to the build folder, in my case: ~\source\repos\MedallionShell\MedallionShell\bin\Debug\net471
  4. add my local version of MedallionShell.dll as the reference library

Unfortunately, this attempt did not resolve the issue.

Shell.cs

I then dug into the Shell.cs, which is the class that invokes the System.Diagnostics.Process built-in class.

I noticed that the default value of CreateNoWindow is set to true in the ProcessStartInfo - see this line.

This is the root cause of the issue - it explains why the window handler becomes null when we shell out azureauth.exe from Medallion Shell.

Solution

The fix here is to allow the configuration to override the default settings. Specifically, createNoWindow should be set to false.

It is very cool that the author of Medallion Shell uses the builder design pattern to implement the GetOptions() method - see this line. This is yet another reason why we should not reinvent the wheels and leverage libraries like Medallion Shell to manage processes.

I found that there’s already a StartInfoInitializers that allows us to modify the processStartInfo object:

finalOptions.StartInfoInitializers.ForEach(a => a(processStartInfo));

What I need to do is pass an arrow function that sets the CreateNoWindow option to false:

var options = new Shell(option => option.StartInfo(info => info.CreateNoWindow = false));
var command = Command.Run(executable, args, options);

Looking Back

This was a very interesting and challenging bug to fix since it involved open source libraries and required a deep understanding of authentication libraries and native Windows APIs.

Even though the final fix was just two lines of code, I did a lot of research along the way.

Now it’s time to ship the code to prod! 🚀