Skip to content

Commit 73bb489

Browse files
Merge pull request #107791 from bgavrilMS/patch-24
Update scenario-desktop-acquire-token-wam.md
2 parents c808a6a + ff1e505 commit 73bb489

File tree

1 file changed

+93
-99
lines changed

1 file changed

+93
-99
lines changed

articles/active-directory/develop/scenario-desktop-acquire-token-wam.md

Lines changed: 93 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -17,139 +17,139 @@ ms.custom: aaddev, devx-track-python
1717

1818
# Desktop app that calls web APIs: Acquire a token using WAM
1919

20-
MSAL is able to call Web Account Manager, a Windows 10 component that ships with the OS. This component acts as an authentication broker and users of your app benefit from integration with accounts known from Windows, such as the account you signed-in with in your Windows session.
21-
22-
## Availability
23-
24-
MSAL 4.25+ supports WAM on UWP, and .NET 5.
25-
26-
For .NET 5, target `net5.0-windows10.0.17763.0` (or higher) and not just `net5.0`. Your app will still run on older versions of Windows if you add `<SupportedOSPlatformVersion>7</SupportedOSPlatformVersion>` in the *.csproj* file. MSAL will use a browser when WAM isn't available.
20+
MSAL is able to call Web Account Manager (WAM), a Windows 10+ component that ships with the OS. This component acts as an authentication broker and users of your app benefit from integration with accounts known from Windows, such as the account you signed-in with in your Windows session.
2721

2822
## WAM value proposition
2923

3024
Using an authentication broker such as WAM has numerous benefits.
3125

32-
- Enhanced security (your app doesn't have to manage the powerful refresh token)
26+
- Enhanced security. See [token protection](https://learn.microsoft.com/azure/active-directory/conditional-access/concept-token-protection)
3327
- Better support for Windows Hello, Conditional Access and FIDO keys
3428
- Integration with Windows' "Email and Accounts" view
35-
- Better Single Sign-On (users don't have to reenter passwords)
29+
- Better Single Sign-On
30+
- Ability to sign in silently with the current Windows account
3631
- Most bug fixes and enhancements will be shipped with Windows
3732

3833
## WAM limitations
3934

35+
- Available on Windows 10 and later and on Windows Server 2019 and later. On Mac, Linux, and earlier versions of Windows, MSAL will automatically fall back to a browser.
4036
- B2C and ADFS authorities aren't supported. MSAL will fall back to a browser.
41-
- Available on Win10+ and Win Server 2019+. On Mac, Linux, and earlier versions of Windows, MSAL will fall back to a browser.
42-
- Not available on Xbox.
4337

44-
## WAM calling pattern
38+
## WAM integration package
4539

46-
You can use the following pattern to use WAM.
40+
Most apps will need to reference `Microsoft.Identity.Client.Broker` package to use this integration. MAUI apps are not required to do this; the functionality is inside MSAL when the target is `net6-windows` and later.
4741

48-
```csharp
49-
// 1. Configuration - read below about redirect URI
50-
var pca = PublicClientApplicationBuilder.Create("client_id")
51-
.WithBroker()
52-
.Build();
53-
54-
// Add a token cache, see https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=desktop
42+
## WAM calling pattern
5543

56-
// 2. GetAccounts
57-
var accounts = await pca.GetAccountsAsync();
58-
var accountToLogin = // choose an account, or null, or use PublicClientApplication.OperatingSystemAccount for the default OS account
44+
You can use the following pattern to use WAM.
5945

60-
try
61-
{
62-
// 3. AcquireTokenSilent
63-
var authResult = await pca.AcquireTokenSilent(new[] { "User.Read" }, accountToLogin)
64-
.ExecuteAsync();
65-
}
66-
catch (MsalUiRequiredException) // no change in the pattern
67-
{
68-
// 4. Specific: Switch to the UI thread for next call . Not required for console apps.
69-
await SwitchToUiThreadAsync(); // not actual code, this is different on each platform / tech
70-
71-
// 5. AcquireTokenInteractive
72-
var authResult = await pca.AcquireTokenInteractive(new[] { "User.Read" })
73-
.WithAccount(accountToLogin) // this already exists in MSAL, but it is more important for WAM
74-
.WithParentActivityOrWindow(myWindowHandle) // to be able to parent WAM's windows to your app (optional, but highly recommended; not needed on UWP)
75-
.ExecuteAsync();
76-
}
46+
```csharp
47+
// 1. Configuration - read below about redirect URI
48+
var pca = PublicClientApplicationBuilder.Create("client_id")
49+
.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
50+
.Build();
51+
52+
// Add a token cache, see https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=desktop
53+
54+
// 2. Find a account for silent login
55+
56+
// is there an account in the cache?
57+
IAccount accountToLogin = (await pca.GetAccountsAsync()).FirstOrDefault();
58+
if (accountToLogin == null)
59+
{
60+
// 3. no account in the cache, try to login with the OS account
61+
accountToLogin = PublicClientApplication.OperatingSystemAccount;
62+
}
63+
64+
try
65+
{
66+
// 4. Silent authentication
67+
var authResult = await pca.AcquireTokenSilent(new[] { "User.Read" }, accountToLogin)
68+
.ExecuteAsync();
69+
}
70+
// cannot login silently - most likely AAD would like to show a consent dialog or the user needs to re-enter credentials
71+
catch (MsalUiRequiredException)
72+
{
73+
// 5. Interactive authentication
74+
var authResult = await pca.AcquireTokenInteractive(new[] { "User.Read" })
75+
.WithAccount(accountToLogin)
76+
// this is mandatory so that WAM is correctly parented to your app, read on for more guidance
77+
.WithParentActivityOrWindow(myWindowHandle)
78+
.ExecuteAsync();
79+
80+
// consider allowing the user to re-authenticate with a different account, by calling AcquireTokenInteractive again
81+
}
7782
```
7883

79-
Call `.WithBroker(true)`. If a broker isn't present (for example, Win8.1, Mac, or Linux), then MSAL will fall back to a browser, where redirect URI rules apply.
84+
If a broker isn't present (for example, Win8.1, Mac, or Linux), then MSAL will fall back to a browser, where redirect URI rules apply.
8085

81-
## Redirect URI
86+
### Redirect URI
8287

83-
WAM redirect URIs don't need to be configured in MSAL, but they must be configured in the app registration.
84-
85-
### Win32 (.NET framework / .NET 5)
88+
WAM redirect URIs don't need to be configured in MSAL, but they must be configured in the app registration.
8689

8790
```
8891
ms-appx-web://microsoft.aad.brokerplugin/{client_id}
8992
```
9093

91-
### UWP
92-
```csharp
93-
// returns smth like S-1-15-2-2601115387-131721061-1180486061-1362788748-631273777-3164314714-2766189824
94-
string sid = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper();
94+
### Token cache persistence
9595

96-
// the redirect uri you need to register
97-
string redirectUri = $"ms-appx-web://microsoft.aad.brokerplugin/{sid}";
98-
```
96+
It's important to persist MSAL's token cache because MSAL continues to store id tokens and account metadata there. See https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=desktop
9997

100-
## Token cache persistence
98+
### Find a account for silent login
10199

102-
It's important to persist MSAL's token cache because MSAL needs to save internal WAM account IDs there. Without it, restarting the app means that `GetAccounts` API will miss some of the accounts. On UWP, MSAL knows where to save the token cache.
100+
The recommended pattern is:
103101

104-
## GetAccounts
102+
1. If the user previously logged in, use that account.
103+
2. If not, use `PublicClientApplication.OperatingSystemAccount` which the current Windows Account
104+
3. Allow the end-user to change to a different account by logging in interactively.
105105

106-
`GetAccounts` returns accounts of users who have previously logged in interactively into the app.
106+
## Parent Window Handles
107107

108-
In addition, WAM can list the OS-wide Work and School accounts configured in Windows (for Win32 apps but not for UWP apps). To opt-into this feature, set `ListWindowsWorkAndSchoolAccounts` in `WindowsBrokerOptions` to **true**. You can enable it as below.
108+
It is required to configure MSAL with the window that the interactive experience should be parented to, using `WithParentActivityOrWindow` APIs.
109109

110-
```csharp
111-
.WithWindowsBrokerOptions(new WindowsBrokerOptions()
112-
{
113-
// GetAccounts will return Work and School accounts from Windows
114-
ListWindowsWorkAndSchoolAccounts = true,
110+
### UI applications
111+
For UI apps like WinForms, WPF, WinUI3 see https://learn.microsoft.com/windows/apps/develop/ui-input/retrieve-hwnd
115112

116-
// Legacy support for 1st party apps only
117-
MsaPassthrough = true
118-
})
119-
```
113+
### Console applications
120114

121-
>[!NOTE]
122-
> Microsoft (outlook.com etc.) accounts will not be listed in Win32 nor UWP for privacy reasons.
115+
For console applications it is a bit more involved, because of the terminal window and its tabs. Use the following code:
123116

124-
Applications cannot remove accounts from Windows!
125-
126-
## RemoveAsync
127-
128-
- Removes all account information from MSAL's token cache (this includes MSA, that is, personal accounts information copied by MSAL into its cache).
129-
- Removes app-only (not OS-wide) accounts.
130-
131-
>[!NOTE]
132-
> Only users can remove OS accounts, whereas apps themselves cannot. If an OS account is passed into `RemoveAsync`, and then `GetAccounts` is called with `ListWindowsWorkAndSchoolAccounts` enabled, the same OS accounts will still be returned.
133-
134-
## Other considerations
135-
136-
- WAM's interactive operations require being on the UI thread. MSAL throws a meaningful exception when not on UI thread. This doesn't apply to console apps.
137-
- `WithAccount` provides an accelerated authentication experience if the MSAL account was originally obtained via WAM, or, WAM can find a work and school account in Windows.
138-
- WAM isn't able to pre-populate the username field with a login hint, unless a Work and School account with the same username is found in Windows.
139-
- If WAM is unable to offer an accelerated authentication experience, it will show an account picker. Users can add new accounts.
140-
141-
!["WAM account picker"](media/scenario-desktop-acquire-token-wam/wam-account-picker.png)
117+
```csharp
118+
enum GetAncestorFlags
119+
{
120+
GetParent = 1,
121+
GetRoot = 2,
122+
/// <summary>
123+
/// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
124+
/// </summary>
125+
GetRootOwner = 3
126+
}
142127

143-
- New accounts are automatically remembered by Windows. Work and School have the option of joining the organization's directory or opting out completely, in which case the account won't appear under "Email & Accounts". Microsoft accounts are automatically added to Windows. Apps can't list these accounts programmatically (but only through the Account Picker).
128+
/// <summary>
129+
/// Retrieves the handle to the ancestor of the specified window.
130+
/// </summary>
131+
/// <param name="hwnd">A handle to the window whose ancestor is to be retrieved.
132+
/// If this parameter is the desktop window, the function returns NULL. </param>
133+
/// <param name="flags">The ancestor to be retrieved.</param>
134+
/// <returns>The return value is the handle to the ancestor window.</returns>
135+
[DllImport("user32.dll", ExactSpelling = true)]
136+
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
137+
138+
[DllImport("kernel32.dll")]
139+
static extern IntPtr GetConsoleWindow();
140+
141+
// This is your window handle!
142+
public IntPtr GetConsoleOrTerminalWindow()
143+
{
144+
IntPtr consoleHandle = GetConsoleWindow();
145+
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner );
146+
147+
return handle;
148+
}
149+
```
144150

145151
## Troubleshooting
146152

147-
### "Either the user canceled the authentication or the WAM Account Picker crashed because the app is running in an elevated process" error message
148-
149-
When an app that uses MSAL is run as an elevated process, some of these calls within WAM may fail due to different process security levels. Internally MSAL.NET uses native Windows methods ([COM](/windows/win32/com/the-component-object-model)) to integrate with WAM. Starting with version 4.32.0, MSAL will display a descriptive error message when it detects that the app process is elevated and WAM returned no accounts.
150-
151-
One solution is to not run the app as elevated, if possible. Another solution is for the app developer to call `WindowsNativeUtils.InitializeProcessSecurity` method when the app starts up. This will set the security of the processes used by WAM to the same levels. See [this sample app](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/master/tests/devapps/WAM/NetCoreWinFormsWam/Program.cs#L18-L21) for an example. However, note, that this solution isn't guaranteed to succeed to due external factors like the underlying CLR behavior. In that case, an `MsalClientException` will be thrown. For more information, see issue [#2560](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2560).
152-
153153
### "WAM Account Picker did not return an account" error message
154154

155155
This message indicates that either the application user closed the dialog that displays accounts, or the dialog itself crashed. A crash might occur if AccountsControl, a Windows control, is registered incorrectly in Windows. To resolve this issue:
@@ -162,16 +162,10 @@ This message indicates that either the application user closed the dialog that d
162162
if (-not (Get-AppxPackage Microsoft.AccountsControl)) { Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown } Get-AppxPackage Microsoft.AccountsControl
163163
```
164164

165-
### Connection issues
166-
167-
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](/office365/troubleshoot/authentication/connection-issue-when-sign-in-office-2016), which also uses WAM.
168-
169165
## Sample
170166

171167
[WPF sample that uses WAM](https://github.com/azure-samples/active-directory-dotnet-desktop-msgraph-v2)
172168

173-
[UWP sample that uses WAM, along Xamarin](https://github.com/Azure-Samples/active-directory-xamarin-native-v2/tree/master/2-With-broker)
174-
175169
---
176170
## Next steps
177171

0 commit comments

Comments
 (0)