Using MSAL.NET with Web Account Manager (WAM)

MSAL is able to call Web Account Manager (WAM), a Windows component that ships with the OS. This component acts as an authentication broker allowing the users of your app to benefit from integration with accounts known to Windows, such as the account you signed into your Windows session.

Note

WAM is available on Windows 10 and above, as well as Windows Server 2019 and above. MSAL will automatically fallback to a browser if WAM cannot be used.

What is a broker

An authentication broker is an application that runs on a user’s machine that manages the authentication handshakes and token maintenance for connected accounts. The Windows operating system uses the Web Account Manager (WAM) as its authentication broker. It has many benefits for developers and customers alike, including:

  • Enhanced security. Many security enhancements will be delivered with the broker, without needing to update the application logic.
  • Feature support. With the help of the broker developers can access rich OS and service capabilities such as Windows Hello, conditional access policies, and FIDO keys without writing extra scaffolding code.
  • System integration. Applications that use the broker plug-and-play with the built-in account picker, allowing the user to quickly pick an existing account instead of reentering the same credentials over and over.
  • Token Protection. WAM ensures that the refresh tokens are device bound and enables apps to acquire device bound access tokens. See Token Protection

Enabling WAM

Important

Use MSAL.NET 4.52.0 or higher to get broker support.

WAM support is split across two packages:

Note

For migration purposes, and if you have a .NET 6, .NET Core, or a .NET Standard application that needs to use both WAM and the embedded browser, you will also need to use the Microsoft.Identity.Client.Desktop package. Once added, developers can use WithWindowsDesktopFeatures when setting up their public client application.

If your application targets UWP or net-windows (version-dependent Target Framework Moniker for Windows), WAM is included in the MSAL.NET package.

After referencing the relevant packages, call WithBroker(BrokerOptions) with broker configuration options and a window handle that the broker will be bound to.

Note

Most apps need to reference the Microsoft.Identity.Client.Broker package to use this integration. .NET MAUI and UWP applications don't need to add the dependency because the functionality is embedded into MSAL.

var scopes = new[] { "User.Read" };

BrokerOptions options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows);
options.Title = "My Awesome Application";

IPublicClientApplication app =
    PublicClientApplicationBuilder.Create("YOUR_CLIENT_ID")
    .WithDefaultRedirectUri()
    .WithParentActivityOrWindow(GetConsoleOrTerminalWindow)
    .WithBroker(options)
    .Build();

AuthenticationResult result = null;

// Try to use the previously signed-in account from the cache
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
IAccount existingAccount = accounts.FirstOrDefault();

try
{    
    if (existingAccount != null)
    {
        result = await app.AcquireTokenSilent(scopes, existingAccount).ExecuteAsync();
    }
    // Next, try to sign in silently with the account that the user is signed into Windows
    else
    {    
        result = await app.AcquireTokenSilent(scopes, PublicClientApplication.OperatingSystemAccount)
                            .ExecuteAsync();
    }
}
// Can't get a token silently, go interactive
catch (MsalUiRequiredException ex)
{
    result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
}

When using the broker, if the authority used is targeting Microsoft Entra ID as well as personal Microsoft accounts, the user will first be prompted to select an account using the built-in system account picker.

Demo of the WAM component

If the configuration is set on a per-tenant basis by using WithTenantId or if the authority is set to an audience that does not include personal Microsoft accounts, the native Windows account picker will not be shown and instead the user will be prompted with a generic Microsoft authentication prompt.

Demo of the WAM component that is configured on a per-tenant basis and doesn't show the OS-based account picker

Once the account is added or selected, the user will be prompted for additional consent if they have never used the application before or the application requires additional permissions.

Parent window handles

To use the broker, it is now required to provide the window handle to which the WAM modal dialog be parented using WithParentActivityOrWindow APIs. The window handle must be provided by the developer because it's not feasible for MSAL itself to infer the parent window and in the past this has led to bad user experiences where the authentication window was hidden behind the application window.

For UI apps, such as those using Windows Forms, Windows Presentation Foundation (WPF), or WinUI3, see Retrieve a window handle (HWND).

For console applications, you can use code like the snippet below.

enum GetAncestorFlags
{   
    GetParent = 1,
    GetRoot = 2,
    /// <summary>
    /// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
    /// </summary>
    GetRootOwner = 3
}

/// <summary>
/// Retrieves the handle to the ancestor of the specified window.
/// </summary>
/// <param name="hwnd">A handle to the window whose ancestor is to be retrieved.
/// If this parameter is the desktop window, the function returns NULL. </param>
/// <param name="flags">The ancestor to be retrieved.</param>
/// <returns>The return value is the handle to the ancestor window.</returns>
[DllImport("user32.dll", ExactSpelling = true)]
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);

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

// This is your window handle!
public IntPtr GetConsoleOrTerminalWindow()
{
    IntPtr consoleHandle = GetConsoleWindow();
    IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner );
    
    return handle;
}

Proof-of-Possession access tokens

The WAM broker allows acquiring PoP tokens for public client flows. See Proof-of-Possession tokens for more details.

Redirect URI

WAM redirect URIs do not need to be configured in MSAL, but they must be configured in the app registration. They should follow the pattern below:

ms-appx-web://microsoft.aad.brokerplugin/{client_id}

Note

When configuring the redirect URL in the Azure Portal, ensure that you're setting it in the Mobile and desktop applications section.

Username/password flow

This flow, also known as Resource Owner Password Credentials (ROPC), is not recommended except in test scenarios or in scenarios where service principal access to a resource gives it too much access and you can only scope it down with user flows. When using WAM, AcquireTokenByUsernamePassword will let WAM manage the protocol and fetch tokens.

Warning

There are a few important considerations that you need to account for when using the ROPC flow. One of the main ones is that it doesn't support personal Microsoft accounts and Microsoft Entra accounts with enabled multi-factor authentication. Check out Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials for the full overview.

WAM limitations

  • Azure B2C and Active Directory Federation Services (ADFS) authorities aren't supported. MSAL will fall back to using a browser for user authentication.
  • On Mac, Linux, and versions of Windows earlier than 10 or Windows Server 2019, MSAL will fall back to a browser.
  • Updated WAM broker is not available on UWP due to Windows API limitations. UWP apps will use the legacy WAM implementation.
  • At this time, WAM uses EdgeHTML as the browser engine for authentication flows. Organizations and identity providers need to ensure that EdgeHTML is an allowed browser engine on customer devices for WAM-based applications to work.

Package availability

To use the broker, developers will need to call WithBroker(PublicClientApplicationBuilder, BrokerOptions), hosted in the Microsoft.Identity.Client.Broker package. Most of the .NET platform variants supported by MSAL.NET will need that package only, with a few exceptions. See the table below for a detailed mapping.

Framework Microsoft.Identity.Client Microsoft.Identity.Client.Broker Microsoft.Identity.Client.Desktop
net48 ✅ (not recommended)
net6.0
net6.0-windows
.NET MAUI

Troubleshooting

"MsalClientException (ErrCode 5376): At least one scope needs to be requested for this authentication flow." error message

This message indicates that you need to request at least one application scope (e.g. user.read) along with other OIDC scopes (profile, email or offline_access).

var authResult = await pca.AcquireTokenInteractive(new[] { "user.read" })
                 .ExecuteAsync();

Account picker does not show up

Sometimes a Windows update can unintentionally affect the account picker component, which shows the list of accounts in Windows and the option to add new accounts. The symptom is that the picker does not come up for a small number of users.

A possible workaround is to re-register the component. Run this script from the terminal with Administrator permissions:

if (-not (Get-AppxPackage Microsoft.AccountsControl))
{ 
    Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown 
}

Get-AppxPackage Microsoft.AccountsControl

Connection issues

The application user sees an error message similar to Please check your connection and try again. If this issue occurs regularly, see the troubleshooting guide for Office, which also uses WAM.

WAM error codes

Refer to Errors associated with Web Account Manager (WAM) for details on WAM errors.

Because WAM is a relatively new component, when errors occur we recommend logging data from AdditionalExceptionData. This can help pinpoint specific issues with the configuration or the WAM component. When you run into WAM issues, please log a bug - this will help us ensure we address the problem in a timely manner.