Skip to content

Commit 9338d91

Browse files
authored
Update scenario-desktop-acquire-token-wam.md
1 parent 9934085 commit 9338d91

File tree

1 file changed

+88
-98
lines changed

1 file changed

+88
-98
lines changed

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

Lines changed: 88 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -17,139 +17,135 @@ 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 Win10+ and Win Server 2019+. 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

4438
## WAM calling pattern
4539

4640
You can use the following pattern to use WAM.
4741

4842
```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
55-
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
59-
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-
}
43+
// 1. Configuration - read below about redirect URI
44+
var pca = PublicClientApplicationBuilder.Create("client_id")
45+
.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows))
46+
.Build();
47+
48+
// Add a token cache, see https://learn.microsoft.com/azure/active-directory/develop/msal-net-token-cache-serialization?tabs=desktop
49+
50+
// 2. Find a account for silent login
51+
52+
// is there an account in the cache?
53+
IAccount accountToLogin = (await pca.GetAccountsAsync()).FirstOrDefault();
54+
if (accountToLogin == null)
55+
{
56+
// 3. no account in the cache, try to login with the OS account
57+
accountToLogin = PublicClientApplication.OperatingSystemAccount;
58+
}
59+
60+
try
61+
{
62+
// 4. Silent authentication
63+
var authResult = await pca.AcquireTokenSilent(new[] { "User.Read" }, accountToLogin)
64+
.ExecuteAsync();
65+
}
66+
// cannot login silently - most likely AAD would like to show a consent dialog or the user needs to re-enter credentials
67+
catch (MsalUiRequiredException)
68+
{
69+
// 5. Interactive authentication
70+
var authResult = await pca.AcquireTokenInteractive(new[] { "User.Read" })
71+
.WithAccount(accountToLogin)
72+
// this is mandatory so that WAM is correctly parented to your app, read on for more guidance
73+
.WithParentActivityOrWindow(myWindowHandle)
74+
.ExecuteAsync();
75+
76+
// consider allowing the user to re-authenticate with a different account, by calling AcquireTokenInteractive again
77+
}
7778
```
7879

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.
80-
81-
## Redirect URI
80+
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.
8281

83-
WAM redirect URIs don't need to be configured in MSAL, but they must be configured in the app registration.
82+
### Redirect URI
8483

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

8786
```
8887
ms-appx-web://microsoft.aad.brokerplugin/{client_id}
8988
```
9089

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();
95-
96-
// the redirect uri you need to register
97-
string redirectUri = $"ms-appx-web://microsoft.aad.brokerplugin/{sid}";
98-
```
90+
### Token cache persistence
9991

100-
## Token cache persistence
92+
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
10193

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.
94+
### Find a account for silent login
10395

104-
## GetAccounts
96+
The recommended pattern is:
10597

106-
`GetAccounts` returns accounts of users who have previously logged in interactively into the app.
98+
1. If the user previously logged in, use that account.
99+
2. If not, use `PublicClientApplication.OperatingSystemAccount` which the current Windows Account
100+
3. Allow the end-user to change to a different account by logging in interactively.
107101

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.
102+
## Parent Window Handles
109103

110-
```csharp
111-
.WithWindowsBrokerOptions(new WindowsBrokerOptions()
112-
{
113-
// GetAccounts will return Work and School accounts from Windows
114-
ListWindowsWorkAndSchoolAccounts = true,
104+
It is required to configure MSAL with the window that the interactive experience should be parented to, using `WithParentActivityOrWindow` APIs.
115105

116-
// Legacy support for 1st party apps only
117-
MsaPassthrough = true
118-
})
119-
```
106+
### UI applications
107+
For UI apps like WinForms, WPF, WinUI3 see https://learn.microsoft.com/en-us/windows/apps/develop/ui-input/retrieve-hwnd
120108

121-
>[!NOTE]
122-
> Microsoft (outlook.com etc.) accounts will not be listed in Win32 nor UWP for privacy reasons.
109+
### Console applications
123110

124-
Applications cannot remove accounts from Windows!
111+
For console applications it is a bit more involved, because of the terminal window and its tabs. Use the following code:
125112

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)
113+
```csharp
114+
enum GetAncestorFlags
115+
{
116+
GetParent = 1,
117+
GetRoot = 2,
118+
/// <summary>
119+
/// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
120+
/// </summary>
121+
GetRootOwner = 3
122+
}
142123

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).
124+
/// <summary>
125+
/// Retrieves the handle to the ancestor of the specified window.
126+
/// </summary>
127+
/// <param name="hwnd">A handle to the window whose ancestor is to be retrieved.
128+
/// If this parameter is the desktop window, the function returns NULL. </param>
129+
/// <param name="flags">The ancestor to be retrieved.</param>
130+
/// <returns>The return value is the handle to the ancestor window.</returns>
131+
[DllImport("user32.dll", ExactSpelling = true)]
132+
static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
133+
134+
[DllImport("kernel32.dll")]
135+
static extern IntPtr GetConsoleWindow();
136+
137+
// This is your window handle!
138+
public IntPtr GetConsoleOrTerminalWindow()
139+
{
140+
IntPtr consoleHandle = GetConsoleWindow();
141+
IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner );
142+
143+
return handle;
144+
}
145+
```
144146

145147
## Troubleshooting
146148

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-
153149
### "WAM Account Picker did not return an account" error message
154150

155151
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 +158,10 @@ This message indicates that either the application user closed the dialog that d
162158
if (-not (Get-AppxPackage Microsoft.AccountsControl)) { Add-AppxPackage -Register "$env:windir\SystemApps\Microsoft.AccountsControl_cw5n1h2txyewy\AppxManifest.xml" -DisableDevelopmentMode -ForceApplicationShutdown } Get-AppxPackage Microsoft.AccountsControl
163159
```
164160

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-
169161
## Sample
170162

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

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

0 commit comments

Comments
 (0)