Integrating Entra ID Authentication into WinForms/WPF Apps — A Practical Architecture with MSAL.NET and the WAM Broker

· · Windows, C#, .NET, WinForms, WPF, Entra ID, Authentication, Security, Technical Consulting

“We built a login screen for every in-house business app and manage passwords in our own database. Every time someone leaves the company, we have to go around disabling their account in each app one by one.” “We’ve rolled out Microsoft 365 company-wide, so can’t we just have people sign in with that same account?” These are themes that keep coming up more and more in desktop app modernization consultations over the past few years. Sometimes it arrives framed differently — the IT department has told the business “stop managing your own passwords,” prompted by a password leak incident or a push toward zero trust.

The short answer: if your organization uses Microsoft 365, shifting the login for your in-house WinForms/WPF apps to Entra ID (formerly Azure AD) is a sound investment. The app stops holding any passwords at all, and tenant-side defenses and auditing — multi-factor authentication, conditional access, sign-in logs — automatically extend to cover your internal apps too. The implementation itself fits within a library called MSAL.NET and a few dozen lines of code.

That said, there are a handful of pitfalls unique to desktop apps. The old-school approach of “take a username and password in a text box and authenticate behind the scenes” (ROPC) is officially on its way to being deprecated, and you must not adopt it for new work. If you don’t persist the token cache, users see the sign-in screen every time the app starts. And on Windows, whether or not you use the broker (WAM) makes a big difference to both the user experience and security. This article walks through everything from a minimal conceptual overview, to app registration, MSAL.NET implementation, WAM, caching, the adoption decision, and operational pitfalls.

1. The Bottom Line First

  • If you drop self-managed IDs and passwords in favor of Entra ID, password storage, reset support, disabling accounts for departed employees, and sign-in auditing all become the tenant’s job. Dramatically shrinking the app’s own responsibility surface is the biggest benefit.
  • A desktop app is a public client. Since the exe can be reverse-engineered wherever it’s distributed, it cannot (and must not) hold a client secret. The app registration must also be configured as a public client. 1
  • ROPC (Resource Owner Password Credentials), where the app itself takes the username and password directly, is officially documented as “deprecated,” with a migration guide published to go along with it. It’s incompatible with MFA and conditional access, and is effectively becoming unusable. Treat adopting it for new work as forbidden. 23
  • The implementation uses MSAL.NET (Microsoft.Identity.Client), and there is really only one basic calling pattern: call AcquireTokenSilent first, and only fall back to AcquireTokenInteractive when you get an MsalUiRequiredException. 4
  • On Windows, authenticating through the WAM (Web Account Manager) broker is the recommended approach. SSO with the account the user is already signed into Windows with, support for conditional access, Windows Hello, and FIDO keys, and device-bound refresh tokens all come for free with a single WithBroker line. 5
  • Forget to persist the token cache, and the sign-in screen reappears every time the app restarts. Build in the encrypted cache from Microsoft.Identity.Client.Extensions.Msal from day one. 6
  • Entra authentication is a network-dependent mechanism. It won’t work for shop-floor apps that need to run fully offline, so use the decision table in Chapter 8 to judge feasibility before you commit.

2. The Big Picture — What It Means to Stop Managing Passwords Yourself

2.1 What’s Wrong with Self-Managed Passwords

When a business app manages passwords in its own user table, the following responsibilities all fall on the app — meaning on us, the developers.

  • Storage: choosing and implementing a hashing scheme (it’s still not uncommon to find 15-year-old tables sitting there with unsalted MD5)
  • Operations: handling password reset inquiries, lockouts, distributing initial passwords
  • Lifecycle: disabling accounts when people leave or transfer. With five apps, that’s five separate account shutdowns
  • Auditing: recording and preserving who logged in when. Multi-factor authentication is effectively impossible to implement

Delegate authentication to Entra ID, and these four responsibilities disappear from the app’s code and consolidate into tenant-level administration. When someone leaves, disabling their Entra ID account instantly blocks them from logging into every app, and sign-in logs are kept automatically. For organizations that have already adopted Microsoft 365, there’s almost no reason left to keep running self-managed authentication. (For organizations on Google Workspace who want the counterpart mechanism — shifting the Windows logon itself to a Google account — see “What Is GCPW?”.)

2.2 The Minimum Concepts — Public Clients and Tokens

Skipping the textbook explanation of OAuth 2.0 / OpenID Connect, here are just the concepts you need for a desktop app implementation.

Concept What it means for a desktop app
Public client An app — an exe, a mobile app, etc. — that cannot safely hold a secret (client secret). It can only obtain tokens on behalf of a user
Confidential client An app such as a web server or a daemon that can hold a secret or a certificate. A desktop app is not this
ID token A JWT representing “who this person is.” This alone is enough if all you need is a login feature
Access token A pass for calling a specific API (Microsoft Graph or your own web API). It has the destination (audience) and scopes baked in
Refresh token A token used to renew the two tokens above without interaction. MSAL manages it automatically inside the cache; the app never sees it directly

The first row is the important one. Since an exe can be analyzed and decompiled wherever it’s distributed, any “secret” embedded in it stops being secret. That’s why the app registers as a public client that runs with no secret, and the actual authentication itself (entering a password, MFA) is delegated to a browser or the OS’s broker, with the app only ever receiving a token. The app never touching the user’s password is the very foundation of this design.

2.3 ROPC Is a Dead End — What the Documentation Actually Says

The old-school instinct is “just build our own login screen that takes a username and password, and have Entra ID verify it behind the scenes.” This is ROPC (passing the username and password directly), and while it still exists in MSAL.NET as AcquireTokenByUsernamePassword, the current official documentation is unambiguous.

  • ROPC for public clients is explicitly documented as “deprecated due to security risk,” with a published migration guide pointing toward safer flows. 3
  • ROPC is incompatible with MFA and conditional access. A user for whom the tenant enforces MFA will simply be blocked and unable to sign in through this flow. 2
  • SSO doesn’t work, personal Microsoft accounts can’t be used, and passwordless accounts (FIDO, Authenticator) can’t sign in either. 2
  • Microsoft’s own web APIs are increasingly moving toward accepting only MFA-satisfied tokens, and the official documentation itself states that “apps that rely on ROPC will be locked out; desktop apps should migrate to broker-based authentication.” 2

Since a tenant can enforce MFA at any time through its own settings, adopting ROPC because “it’s working right now” is a recipe for waking up one day to find every single user locked out. Even if an existing app currently runs on ROPC, plan for migration. Practically speaking, there are only two token-acquisition flows a desktop app should use.

Flow Where it’s used
Interactive (broker / browser) Ordinary GUI apps. The main choice
Device code flow Environments where a browser cannot be shown (a console over SSH, etc.). Displays a URL and a code, and the user signs in from a browser on a separate device

3. App Registration — Configuring It in the Entra Admin Center

Before writing any code, you register the app with the tenant. If a developer can’t do this themselves, treat this section as a ready-made request document to hand to the IT department.

3.1 The Registration Itself

Create it under [App registrations] → [New registration] in the Microsoft Entra admin center (entra.microsoft.com). 7

  • Name: shows up on the consent screen and in sign-in logs, so use something the business side recognizes, like “Inventory Management System.”
  • Supported account types: for an in-house app, “Accounts in this organizational directory only” (single tenant) is the only sensible choice. Multi-tenant is only for products distributed to multiple organizations.
  • Note down the Application (client) ID and Directory (tenant) ID shown after registration and embed them in the app’s configuration (neither is secret).

3.2 Redirect URIs — Choose the “Mobile and Desktop Applications” Platform

This declares where the app receives the token after authentication. Under [Authentication] → [Add a platform] → [Mobile and desktop applications], register the URI appropriate to the authentication method you’ll use. 1

Authentication method Redirect URI to register
WAM broker (the main choice, Chapter 5) ms-appx-web://microsoft.aad.brokerplugin/{client ID}
System browser http://localhost
Embedded browser https://login.microsoftonline.com/common/oauth2/nativeclient

You never write the WAM ms-appx-web://... value in your MSAL code, but it is mandatory on the app registration side. 8 Given the browser fallback that occurs when WAM isn’t available (Chapter 5), the practical approach is to register all three of the URIs in the table above from the start. One point that deserves particular attention: WithDefaultRedirectUri()’s resolution is platform-dependent — it resolves to https://login.microsoftonline.com/common/oauth2/nativeclient on .NET Framework, and to http://localhost on .NET (Core and later). 9 If a .NET Framework app has only registered ms-appx-web and http://localhost, and WAM falls back to the browser, you’ll get an authentication error from the mismatch with nativeclient. Either register all three, or pin it explicitly with WithRedirectUri(...). Accidentally registering under the “Web” platform instead is another classic stumbling block that produces an authentication error.

Under [API permissions], add the delegated permissions for the APIs the app will call. If all you need is login and displaying the profile, the default-granted Microsoft Graph User.Read is sufficient.

Once added, have someone run [Grant admin consent for (tenant name)]. 7 This eliminates the per-user consent dialog on the first sign-in. In tenants where user consent is disabled, the first sign-in stops at “admin approval required” without admin consent, so as a rule, complete admin consent before distributing an internally-distributed app.

3.4 The “Allow Public Client Flows” Flag

The “Allow public client flows” toggle in the advanced Authentication settings is set to “Yes” when you use a flow that doesn’t rely on a redirect URI, such as the device code flow or Integrated Windows Authentication. 1 It’s not required for interactive (browser/broker) alone. Also note that this app registration never creates a client secret or a certificate. Having an empty “Certificates & secrets” section is the correct state for a public client (this gets confused often enough that Chapter 9 comes back to it).

4. Implementing It with MSAL.NET — The Silent-then-Interactive Basic Pattern

Add Microsoft.Identity.Client via NuGet. There’s really only one implementation pattern to memorize: always call AcquireTokenSilent first, and fall back to interactive only when you receive an MsalUiRequiredException. AcquireTokenInteractive is designed to never look at the cache at all, so calling it directly shows the sign-in screen every single time. 4

using Microsoft.Identity.Client;

public sealed class AuthService
{
    private const string ClientId = "Application (client) ID";
    private const string TenantId = "Directory (tenant) ID";
    private static readonly string[] Scopes = { "User.Read" };

    private readonly IPublicClientApplication _app;

    public AuthService()
    {
        _app = PublicClientApplicationBuilder.Create(ClientId)
            .WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
            .WithRedirectUri("http://localhost")  // for the system browser
            .Build();
        // In production, register token cache persistence here (Chapter 6)
    }

    public async Task<AuthenticationResult> SignInAsync(IntPtr ownerHwnd)
    {
        // 1. Always try silent acquisition against a cached account first
        var accounts = await _app.GetAccountsAsync();
        var account = accounts.FirstOrDefault();
        try
        {
            return await _app.AcquireTokenSilent(Scopes, account)
                             .ExecuteAsync();
        }
        catch (MsalUiRequiredException)
        {
            // 2. Only show the sign-in screen when interaction is required.
            // The .NET Framework default is the old-style embedded WebView,
            // so we explicitly specify http://localhost redirect = system browser
            // (.NET 6+ only ever uses the system browser anyway)
            return await _app.AcquireTokenInteractive(Scopes)
                             .WithAccount(account)
                             .WithParentActivityOrWindow(ownerHwnd)
                             .WithUseEmbeddedWebView(false)
                             .ExecuteAsync();
        }
    }
}

The caller passes in the owner window’s handle. This prevents the authentication dialog from being hidden behind the app’s own window — a mistake that’s mandatory to avoid with WAM. 5 One more thing: don’t omit WithUseEmbeddedWebView(false) in a .NET Framework app. .NET Framework’s interactive authentication default is the embedded WebView, while the http://localhost redirect is meant for the system browser. (Get the combination wrong, and you either fall back to the old-style embedded browser — which doesn’t support conditional access or Windows Hello/FIDO — or you get a redirect URI mismatch.) 10 On .NET 6 and later there’s no embedded WebView at all, so it’s always the system browser; this call is redundant there, but harmless.

// WinForms (inside a Form method)
var result = await _authService.SignInAsync(this.Handle);

// WPF
var hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
var result = await _authService.SignInAsync(hwnd);

this.Text = $"Signed in as: {result.Account.Username}";

A few points worth calling out.

  • Use a single IPublicClientApplication instance for the whole app. Since the cache is scoped to the instance, creating a new one via Create on every call defeats silent acquisition.
  • MsalUiRequiredException isn’t an “error” — it’s normal, expected control flow that means interaction is required. It happens on first launch, when the refresh token has expired, or when conditional access requirements change.
  • The correct usage is to call AcquireTokenSilent right before every API call. If there’s a valid token in the cache it returns immediately, and it’s refreshed automatically as its expiry approaches. 4 You must not hold onto the access token yourself and manage its lifetime.
  • Waiting on the UI thread with .Result or .Wait() will deadlock (see “A One-Page Cheat Sheet for async and the UI Thread in WPF/WinForms”).

The code in Chapter 4 pops open a browser, but on Windows there’s a better option available. WAM (Web Account Manager) is an authentication broker built into Windows 10 (1703+) and Windows Server 2019+, and the official documentation lists four advantages. 5

  • Stronger security: refresh tokens are bound to the device, so even if stolen they can’t be used on another machine (token protection). Security improvements continue to arrive through OS updates.
  • Feature support: OS/service-integrated authentication features like Windows Hello, conditional access, and FIDO keys work with no extra code.
  • System integration: accounts already signed into Windows appear in the built-in account picker, so sign-in usually completes with no password entry at all — effectively SSO.
  • Token protection: it can honor conditional access token protection policies.

If the office PC is Entra joined (or Hybrid joined), the experience becomes: launch the app → pick your Windows account → you’re logged in instantly, without typing a password anywhere.

5.1 Implementation — WithBroker and the Package

Using WAM requires MSAL.NET 4.52.0 or later and the additional package Microsoft.Identity.Client.Broker. 5 Add WithBroker to the builder from Chapter 4.

using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;  // for WithBroker(BrokerOptions)

var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
    Title = "Inventory Management System"  // title shown in the account picker
};

_app = PublicClientApplicationBuilder.Create(ClientId)
    .WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
    .WithDefaultRedirectUri()
    .WithParentActivityOrWindow(() => _ownerHwnd)  // mandatory with WAM
    .WithBroker(brokerOptions)
    .Build();

You can strengthen the silent acquisition side by one more line. When there’s no account in the cache, passing PublicClientApplication.OperatingSystemAccount lets you attempt a silent sign-in using “whichever account is currently signed into Windows.” This is the officially recommended pattern for getting a dialog-free login working from the very first launch. 8

var accounts = await _app.GetAccountsAsync();
var account = accounts.FirstOrDefault()
              ?? PublicClientApplication.OperatingSystemAccount;
try
{
    return await _app.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
    return await _app.AcquireTokenInteractive(Scopes).ExecuteAsync();
}

Alongside this, as covered in Section 3.2, register ms-appx-web://microsoft.aad.brokerplugin/{client ID} under the “Mobile and desktop applications” platform on the app registration side. 5 Forgetting this causes interactive authentication to fail with a broker error. One more point: the WithDefaultRedirectUri() in the sample above only determines the redirect URI for when WAM isn’t available and it falls back to the browser, and its resolution is platform-dependent (.NET Framework → nativeclient, .NET → http://localhost). 9 Either way works if you’ve registered all three URIs from the Section 3.2 table, but if you want to keep the registration narrower, pin it explicitly with WithRedirectUri(...).

5.2 WAM’s Constraints — Things That Will Trip You Up If You Don’t Know Them

Constraint Details
OS Windows 10 (1703)+ / Windows Server 2019+. On earlier versions, Mac, or Linux it automatically falls back to the browser 5
Identity provider Entra ID only. Azure AD B2C and AD FS authorities aren’t supported (falls back to the browser) 5
Execution context Assumes it can show UI within an interactive user session. Windows services, Task Scheduler (outside a user session), or running as another user via runas will error by design 5

The third row matters especially. “It works fine in the GUI app, but the same code fails when reused in a nightly batch job” is expected behavior, not a bug. Unattended execution is an area where you need to split the design toward application permissions (a confidential client) rather than reusing a delegated user token. Because the fallback is built in as a designed feature, “try WAM first, fall back to the browser if that fails” is something MSAL gives you in a single line of code.

6. Persisting the Token Cache — Don’t Show the Sign-In Screen on Every Restart

MSAL.NET’s token cache is in-memory only by default, and persisting it is the app’s own responsibility on desktop. Without persistence, AcquireTokenSilent fails on every process restart and falls back to interactive sign-in. 4 “It worked fine during the pilot, but users complained that the login screen showed up every single morning” — this is almost always the cause.

The official recommendation is to use the cross-platform cache library Microsoft.Identity.Client.Extensions.Msal (NuGet). 6

using Microsoft.Identity.Client.Extensions.Msal;

var storageProperties = new StorageCreationPropertiesBuilder(
        "msal_cache.dat",
        Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "KomuraSoft", "InventoryApp"))
    .Build();

var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_app.UserTokenCache);  // register once, right after Build()

On Windows, the cache is stored encrypted. The example of a hand-rolled implementation shown in the official documentation encrypts the token with ProtectedData (DPAPI, DataProtectionScope.CurrentUser) before saving it to a file — Extensions.Msal is positioned as a production-quality library built on that same idea. 6 The principle that “per-user secrets should be protected with user-scoped DPAPI” is the same one covered for configuration files in “Storing Sensitive Data in Windows Apps — Avoiding Plaintext Settings with DPAPI.” Treat a homegrown implementation that saves the token cache as plaintext JSON with the same severity as storing a connection string in plaintext.

Three operational notes.

  • Cache persistence is necessary even when using WAM. MSAL still stores the ID token and account metadata in its own cache. 8
  • The storage location is generally %LOCALAPPDATA%\CompanyName\AppName. Due to DPAPI’s binding, decryption won’t work on a different PC or under a different user, but the only consequence is that silent acquisition fails and falls back to re-sign-in — there’s no real harm.
  • Implement “log out” by enumerating accounts with GetAccountsAsync and removing them with RemoveAsync; you don’t need to delete the cache file. However, RemoveAsync only clears MSAL’s local cache — the WAM, browser, or Windows sign-in session all remain intact. Since the next interactive sign-in can silently re-authenticate the same account, if you need genuine account switching to work on a shared PC, design for it explicitly: either attach WithPrompt(Prompt.SelectAccount) to AcquireTokenInteractive so the account picker is always shown, or, depending on your requirements, also use the tenant’s logout endpoint. Distinguish clearly between “clearing the local cache” and “a true sign-out.”

7. What to Do with the Acquired Token — Three Configurations

Once authentication succeeds, what you do with it splits into three patterns. How far you go determines what additional configuration you need.

Configuration Token used What else is needed
(1) Login only ID token (AuthenticationResult.Account / ClaimsPrincipal) Nothing (User.Read alone is enough)
(2) Calling Microsoft Graph Access token for Graph Graph permissions and consent matching the APIs you want to call
(3) Protecting your own web API Access token for your own API App registration and scope exposure on the API side, plus token validation on the API side

7.1 Login-Only Configuration — Start as Small as Possible

If all you want is to “replace our own password check, without calling any cloud APIs,” it’s enough to just match the account information from the sign-in result against your app’s own permissions table. Replace the key in your users table with the Entra object ID (which stays stable even if the UPN changes, e.g. after a surname change) and remove the password column. Since you don’t need to change the local DB design and are only swapping out authentication, this is the easiest first step to recommend.

7.2 Calling Microsoft Graph

Just throw the User.Read access token straight at Microsoft Graph and you can retrieve the signed-in user’s profile or photo.

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", result.AccessToken);
var me = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me");

If you extend into calendars, sending mail, Teams notifications, and so on, add the corresponding permission (Mail.Send, etc.) and re-obtain admin consent. Consolidating notification emails from in-house apps onto Graph also fits well with the approach discussed in “Designing Mass Email Delivery for SMEs Without Locking Into a Specific Vendor.”

7.3 Protecting Your Own Web API — Validating the Audience and Scopes

If your desktop app calls your own company’s web API, you’ll create a separate app registration for the API side too, expose a scope such as api://{API's client ID}/access_as_user, and have the desktop side request a token with that scope. What matters on the API side is that [Authorize] alone is officially documented as not being enough. 11 Three things need validating.

  1. Signature and issuer: is this a JWT issued by the correct tenant’s Entra ID (with ASP.NET Core + Microsoft.Identity.Web, the middleware handles this)
  2. Audience (aud): is the token’s destination this API itself? Don’t let a token issued for Graph get reused against your own API
  3. Scope (scp claim): does it contain the expected scope? With Microsoft.Identity.Web, this can be declared with the [RequiredScope("access_as_user")] attribute 11

Skip steps 2 and 3, and you end up with “an API that lets anyone through as long as the token looks like it came from Entra.” Include this alongside the communication and input-validation items in “The Minimum Security Checklist for Windows App Development” as a design review target.

8. The Adoption Decision — Should a Fully Internal Tool Get Entra Authentication?

Not every in-house app should have this added. Here’s a decision table.

Situation Recommendation Rationale
Microsoft 365 / Entra ID is rolled out company-wide, and the app already has a login concept Adopt it The entire self-managed password liability disappears. Implementation cost is small
The app calls your own web API or cloud resources Adopt it An authentication foundation is essential for protecting the API. More reliable than inventing your own token scheme
There’s an audit requirement (a record of who used it when, mandatory MFA) Adopt it Sign-in logs and conditional access are consolidated on the tenant side
A single-purpose tool with no login concept (a converter, a viewer, etc.) Not needed There’s no reason to add authentication. The Windows logon is already sufficient
Runs in a fully offline environment (an air-gapped production line, an offsite laptop) Not feasible, or requires careful design The initial sign-in and token refresh both require the network
Entra ID isn’t adopted (on-prem AD only, or Google Workspace only) Look at alternatives For the former, AD authentication (Integrated Windows Authentication); for the latter, a Google-side mechanism is the natural fit

Pay particular attention to the offline requirement. AcquireTokenSilent can return a token offline as long as the cached access token is still valid (rule of thumb: a bit over an hour), but once it expires, refreshing it requires the network. Before adopting, you need to check this token lifetime assumption against how the workflow actually plays out in the field.

9. Operational Pitfalls — Inquiries That Show Up After Rollout

Rollout isn’t the end of the story — there are recurring inquiries that come up during the operational phase. Here they are, ahead of time.

  • “It worked yesterday, but suddenly I can’t log in”: the prime suspect is a change to the conditional access policy. When IT enables something like “block unregistered devices,” sign-in starts failing without the app itself having changed at all. The fastest way to triage it is checking the error reason for the affected user in the Entra admin center’s sign-in logs. A WAM configuration makes the app more resilient to these policy demands, which itself reduces this kind of friction. 5
  • “I got a notice that a secret is about to expire — is this app okay?”: public clients never have a secret or a certificate to begin with, so there’s nothing to expire. If this question comes up, it’s either a mix-up with a confidential client’s app registration, or someone created an unnecessary secret on a public client registration (in which case it’s fine to delete it). The fact that a secret-expiry outage structurally cannot happen is a hidden benefit of this setup.
  • “On first launch, it says ‘admin approval required’“: this is a missing admin consent from Section 3.3. Even if you later add a new permission, the same message reappears until you re-obtain consent for the addition.
  • “I hooked it into a nightly batch job and it doesn’t work”: as covered in Section 5.2, WAM assumes an interactive session. Design unattended processing around application permissions rather than reusing a delegated user token.
  • Distribution and updates: MSAL is actively patched, so you need a mechanism that gets library updates out to every endpoint. Consider this together with the update-path verification covered in “Security Design for Auto-Updates.”

10. Summary

Adding Entra ID authentication support to a WinForms/WPF app boils down to these six points.

  • The point itself is to stop managing passwords yourself. Storage, resets, handling departed employees, and auditing all consolidate on the tenant side
  • A desktop app is a public client. It can’t hold a secret, and it doesn’t need one
  • ROPC is headed toward deprecation. Don’t build a new screen that takes a username and password directly
  • The implementation is unequivocally MSAL.NET’s AcquireTokenSilentAcquireTokenInteractive pattern
  • On Windows, the WAM broker (WithBroker) gets you SSO, conditional access, and Windows Hello support
  • Build in token cache persistence (Extensions.Msal / DPAPI protection) from the very start

With the minimal “swap out login only” configuration (Section 7.1), the impact on an existing app can be limited to the login screen and the user table, and the change often fits within a few days of work. On the other hand, once conditional access or offline requirements come into play, you need design decisions grounded in both the tenant’s configuration and how the business actually operates. If you’re unsure how far to take your app’s configuration, or how to sequence the migration away from self-managed authentication, we’re happy to help.

Komura Software LLC handles integrating Entra ID authentication into existing WinForms/WPF apps (app registration design, MSAL.NET implementation, migration planning from self-managed authentication), design reviews for token validation in your own web APIs, and triaging sign-in failures related to conditional access.

References

</content>

  1. Microsoft Learn, Desktop app that calls web APIs: Code configuration. On redirect URIs for desktop apps (the mobile and desktop platform, nativeclient / localhost) and the meaning of the “Allow public client flows” setting.  2 3

  2. Microsoft Learn, Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials. On why ROPC shouldn’t be used, its incompatibility with MFA and the resulting blocking, the trend toward locking out ROPC-dependent apps, and the recommendation for desktop apps to migrate to broker-based authentication.  2 3 4

  3. Microsoft Learn, Desktop app that calls web APIs: Acquire a token using username and password. On the username/password flow (ROPC) being deprecated due to security risk, guidance toward migration, and its lack of support for MFA, conditional access, and SSO.  2

  4. Microsoft Learn, Get a token from the token cache using MSAL.NET. On the recommended pattern of calling AcquireTokenSilent first and falling back to interactive on MsalUiRequiredException, automatic renewal via the cache and refresh token, and clearing the cache by removing an account.  2 3 4

  5. Microsoft Learn, Using MSAL.NET with Web Account Manager (WAM). On the broker’s advantages (stronger security, support for Windows Hello / conditional access / FIDO, the account picker, token protection), the MSAL.NET 4.52.0+ and Microsoft.Identity.Client.Broker package requirements, the mandatory WithBroker and parent window handle, the ms-appx-web redirect URI, supported OS versions and fallback behavior, and the requirement of an interactive session.  2 3 4 5 6 7 8 9

  6. Microsoft Learn, Token cache serialization. On the recommendation for desktop apps to use Microsoft.Identity.Client.Extensions.Msal’s cross-platform cache, how to use MsalCacheHelper, and the example of a hand-rolled serialization using ProtectedData (DPAPI, CurrentUser scope).  2 3

  7. Microsoft Learn, Register an application with the Microsoft identity platform. On the app registration procedure in the Entra admin center, choosing supported account types, obtaining the client ID, and admin consent.  2

  8. Microsoft Learn, Desktop app that calls web APIs: Acquire a token by using WAM. On the need for token cache persistence even when using WAM, the recommended silent sign-in pattern using OperatingSystemAccount, and redirect URI configuration on the app registration side.  2 3

  9. Microsoft Learn, Default reply URI. On how the redirect URI set by WithDefaultRedirectUri is platform-dependent (https://login.microsoftonline.com/common/oauth2/nativeclient for .NET Framework desktop, http://localhost for .NET Core).  2

  10. Microsoft Learn, Using web browsers (MSAL.NET). On the per-framework browser support table (.NET Framework 4.6.2+ defaults to embedded, .NET 6+ only uses the system browser), the requirement of an http://localhost redirect URI for the system browser, and switching via WithUseEmbeddedWebView. 

  11. Microsoft Learn, Protected web API: Verify scopes and app roles. On why the [Authorize] attribute alone isn’t sufficient, the need to validate the scp claim (scope), and declarative validation via Microsoft.Identity.Web’s RequiredScope attribute.  2

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog