@@ -25,16 +25,18 @@ public static BrokerTokenResult AcquireTokenForResource(
2525 string ? tenant ,
2626 string resourceUrl ,
2727 string clientId ,
28+ string ? accountUsername ,
2829 bool forcePrompt )
2930 {
3031 var scopeCandidates = BuildScopeCandidatesFromResource ( resourceUrl ) ;
31- return AcquireTokenAsync ( tenant , scopeCandidates , clientId , forcePrompt ) . GetAwaiter ( ) . GetResult ( ) ;
32+ return AcquireTokenAsync ( tenant , scopeCandidates , clientId , accountUsername , forcePrompt ) . GetAwaiter ( ) . GetResult ( ) ;
3233 }
3334
3435 public static BrokerTokenResult AcquireTokenForScope (
3536 string ? tenant ,
3637 string scope ,
3738 string clientId ,
39+ string ? accountUsername ,
3840 bool forcePrompt )
3941 {
4042 var scopes = SplitScopes ( scope ) ;
@@ -43,13 +45,14 @@ public static BrokerTokenResult AcquireTokenForScope(
4345 throw new ArgumentException ( "At least one scope must be provided." , nameof ( scope ) ) ;
4446 }
4547
46- return AcquireTokenAsync ( tenant , new [ ] { scopes } , clientId , forcePrompt ) . GetAwaiter ( ) . GetResult ( ) ;
48+ return AcquireTokenAsync ( tenant , new [ ] { scopes } , clientId , accountUsername , forcePrompt ) . GetAwaiter ( ) . GetResult ( ) ;
4749 }
4850
4951 private static async Task < BrokerTokenResult > AcquireTokenAsync (
5052 string ? tenant ,
5153 IReadOnlyList < string [ ] > scopeCandidates ,
5254 string clientId ,
55+ string ? accountUsername ,
5356 bool forcePrompt )
5457 {
5558 if ( ! OperatingSystem . IsWindows ( ) )
@@ -86,7 +89,8 @@ private static async Task<BrokerTokenResult> AcquireTokenAsync(
8689 {
8790 try
8891 {
89- var result = await AcquireTokenForScopesAsync ( application , scopes , forcePrompt ) . ConfigureAwait ( false ) ;
92+ var result = await AcquireTokenForScopesAsync ( application , scopes , accountUsername , forcePrompt ) . ConfigureAwait ( false ) ;
93+ EnsureAccountMatches ( result , accountUsername ) ;
9094 return ToResult ( result ) ;
9195 }
9296 catch ( MsalException ex ) when ( CanTryNextScopeCandidate ( ex ) )
@@ -105,12 +109,20 @@ private static async Task<BrokerTokenResult> AcquireTokenAsync(
105109 private static async Task < AuthenticationResult > AcquireTokenForScopesAsync (
106110 IPublicClientApplication application ,
107111 string [ ] scopes ,
112+ string ? accountUsername ,
108113 bool forcePrompt )
109114 {
115+ var expectedUsername = string . IsNullOrWhiteSpace ( accountUsername ) ? null : accountUsername . Trim ( ) ;
110116 if ( ! forcePrompt )
111117 {
112118 var accounts = await application . GetAccountsAsync ( ) . ConfigureAwait ( false ) ;
113- foreach ( var account in accounts )
119+ var candidateAccounts = string . IsNullOrWhiteSpace ( expectedUsername )
120+ ? accounts
121+ : accounts . Where ( account =>
122+ ! string . IsNullOrWhiteSpace ( account . Username )
123+ && account . Username . Equals ( expectedUsername , StringComparison . OrdinalIgnoreCase ) ) ;
124+
125+ foreach ( var account in candidateAccounts )
114126 {
115127 try
116128 {
@@ -121,20 +133,29 @@ private static async Task<AuthenticationResult> AcquireTokenForScopesAsync(
121133 }
122134 }
123135
124- try
125- {
126- return await application
127- . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount )
128- . ExecuteAsync ( )
129- . ConfigureAwait ( false ) ;
130- }
131- catch ( MsalUiRequiredException )
136+ if ( string . IsNullOrWhiteSpace ( expectedUsername ) )
132137 {
138+ try
139+ {
140+ return await application
141+ . AcquireTokenSilent ( scopes , PublicClientApplication . OperatingSystemAccount )
142+ . ExecuteAsync ( )
143+ . ConfigureAwait ( false ) ;
144+ }
145+ catch ( MsalUiRequiredException )
146+ {
147+ }
133148 }
134149 }
135150
136- return await application
137- . AcquireTokenInteractive ( scopes )
151+ var interactiveBuilder = application
152+ . AcquireTokenInteractive ( scopes ) ;
153+ if ( ! string . IsNullOrWhiteSpace ( expectedUsername ) )
154+ {
155+ interactiveBuilder = interactiveBuilder . WithLoginHint ( expectedUsername ) ;
156+ }
157+
158+ return await interactiveBuilder
138159 . WithPrompt ( Prompt . SelectAccount )
139160 . ExecuteAsync ( )
140161 . ConfigureAwait ( false ) ;
@@ -193,12 +214,43 @@ private static bool CanTryNextScopeCandidate(MsalException exception)
193214 || exception . ErrorCode . Equals ( "invalid_request" , StringComparison . OrdinalIgnoreCase ) ;
194215 }
195216
217+ private static void EnsureAccountMatches ( AuthenticationResult result , string ? expectedUsername )
218+ {
219+ if ( string . IsNullOrWhiteSpace ( expectedUsername ) || result . Account is null )
220+ {
221+ return ;
222+ }
223+
224+ if ( string . IsNullOrWhiteSpace ( result . Account . Username )
225+ || ! result . Account . Username . Equals ( expectedUsername . Trim ( ) , StringComparison . OrdinalIgnoreCase ) )
226+ {
227+ throw new InvalidOperationException (
228+ $ "MSAL WAM returned account '{ result . Account . Username } ' while O365Essentials expected '{ expectedUsername } '.") ;
229+ }
230+ }
231+
196232 private static IntPtr GetConsoleOrTerminalWindow ( )
197233 {
198234 var consoleHandle = GetConsoleWindow ( ) ;
199- return consoleHandle == IntPtr . Zero
200- ? IntPtr . Zero
201- : GetAncestor ( consoleHandle , GetAncestorFlags . GetRootOwner ) ;
235+ if ( consoleHandle != IntPtr . Zero )
236+ {
237+ var owner = GetAncestor ( consoleHandle , GetAncestorFlags . GetRootOwner ) ;
238+ return owner == IntPtr . Zero ? consoleHandle : owner ;
239+ }
240+
241+ var foregroundHandle = GetForegroundWindow ( ) ;
242+ if ( foregroundHandle != IntPtr . Zero )
243+ {
244+ return foregroundHandle ;
245+ }
246+
247+ var shellHandle = GetShellWindow ( ) ;
248+ if ( shellHandle != IntPtr . Zero )
249+ {
250+ return shellHandle ;
251+ }
252+
253+ return GetDesktopWindow ( ) ;
202254 }
203255
204256 private enum GetAncestorFlags
@@ -209,6 +261,15 @@ private enum GetAncestorFlags
209261 [ DllImport ( "kernel32.dll" ) ]
210262 private static extern IntPtr GetConsoleWindow ( ) ;
211263
264+ [ DllImport ( "user32.dll" ) ]
265+ private static extern IntPtr GetForegroundWindow ( ) ;
266+
267+ [ DllImport ( "user32.dll" ) ]
268+ private static extern IntPtr GetShellWindow ( ) ;
269+
270+ [ DllImport ( "user32.dll" ) ]
271+ private static extern IntPtr GetDesktopWindow ( ) ;
272+
212273 [ DllImport ( "user32.dll" , ExactSpelling = true ) ]
213274 private static extern IntPtr GetAncestor ( IntPtr hwnd , GetAncestorFlags flags ) ;
214275}
0 commit comments