66using System . Net . Http ;
77using System . Net . Http . Headers ;
88using System . Reflection ;
9+ using System . Threading ;
910using System . Threading . Tasks ;
11+ using Microsoft . Graph ;
1012using Microsoft . Identity . Client ;
1113
1214namespace CommunityToolkit . Authentication
@@ -16,6 +18,8 @@ namespace CommunityToolkit.Authentication
1618 /// </summary>
1719 public class MsalProvider : BaseProvider
1820 {
21+ private static readonly SemaphoreSlim SemaphoreSlim = new ( 1 ) ;
22+
1923 /// <inheritdoc />
2024 public override string CurrentAccountId => _account ? . HomeAccountId ? . Identifier ;
2125
@@ -47,7 +51,7 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri
4751 . WithClientVersion ( Assembly . GetExecutingAssembly ( ) . GetName ( ) . Version . ToString ( ) )
4852 . Build ( ) ;
4953
50- Scopes = scopes ?? new string [ ] { string . Empty } ;
54+ Scopes = scopes . Select ( s => s . ToLower ( ) ) . ToArray ( ) ?? new string [ ] { string . Empty } ;
5155
5256 Client = client ;
5357
@@ -60,7 +64,23 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri
6064 /// <inheritdoc/>
6165 public override async Task AuthenticateRequestAsync ( HttpRequestMessage request )
6266 {
63- string token = await GetTokenAsync ( ) ;
67+ string token ;
68+
69+ // Check if any specific scopes are being requested.
70+ if ( request . Properties . TryGetValue ( nameof ( GraphRequestContext ) , out object requestContextObj ) &&
71+ requestContextObj is GraphRequestContext requestContext &&
72+ requestContext . MiddlewareOptions . TryGetValue ( nameof ( AuthenticationHandlerOption ) , out IMiddlewareOption optionsMiddleware ) &&
73+ optionsMiddleware is AuthenticationHandlerOption options &&
74+ options . AuthenticationProviderOption ? . Scopes != null && options . AuthenticationProviderOption . Scopes . Length > 0 )
75+ {
76+ var withScopes = options . AuthenticationProviderOption . Scopes ;
77+ token = await this . GetTokenWithScopesAsync ( withScopes ) ;
78+ }
79+ else
80+ {
81+ token = await this . GetTokenAsync ( ) ;
82+ }
83+
6484 request . Headers . Authorization = new AuthenticationHeaderValue ( "Bearer" , token ) ;
6585 }
6686
@@ -119,42 +139,63 @@ public override async Task SignOutAsync()
119139 }
120140
121141 /// <inheritdoc/>
122- public override async Task < string > GetTokenAsync ( bool silentOnly = false )
142+ public override Task < string > GetTokenAsync ( bool silentOnly = false )
123143 {
124- AuthenticationResult authResult = null ;
144+ return this . GetTokenWithScopesAsync ( Scopes , silentOnly ) ;
145+ }
146+
147+ private async Task < string > GetTokenWithScopesAsync ( string [ ] scopes , bool silentOnly = false )
148+ {
149+ await SemaphoreSlim . WaitAsync ( ) ;
150+
125151 try
126152 {
127- var account = _account ?? ( await Client . GetAccountsAsync ( ) ) . FirstOrDefault ( ) ;
128- if ( account != null )
153+ AuthenticationResult authResult = null ;
154+ try
129155 {
130- authResult = await Client . AcquireTokenSilent ( Scopes , account ) . ExecuteAsync ( ) ;
156+ var account = _account ?? ( await Client . GetAccountsAsync ( ) ) . FirstOrDefault ( ) ;
157+ if ( account != null )
158+ {
159+ authResult = await Client . AcquireTokenSilent ( scopes , account ) . ExecuteAsync ( ) ;
160+ }
131161 }
132- }
133- catch ( MsalUiRequiredException )
134- {
135- }
136- catch
137- {
138- // Unexpected exception
139- // TODO: Send exception to a logger.
140- }
141-
142- if ( authResult == null && ! silentOnly )
143- {
144- try
162+ catch ( MsalUiRequiredException )
145163 {
146- authResult = await Client . AcquireTokenInteractive ( Scopes ) . WithPrompt ( Prompt . SelectAccount ) . ExecuteAsync ( ) ;
147164 }
148165 catch
149166 {
150167 // Unexpected exception
151168 // TODO: Send exception to a logger.
152169 }
153- }
154170
155- _account = authResult ? . Account ;
171+ if ( authResult == null && ! silentOnly )
172+ {
173+ try
174+ {
175+ if ( _account != null )
176+ {
177+ authResult = await Client . AcquireTokenInteractive ( scopes ) . WithPrompt ( Prompt . NoPrompt ) . WithAccount ( _account ) . ExecuteAsync ( ) ;
178+ }
179+ else
180+ {
181+ authResult = await Client . AcquireTokenInteractive ( scopes ) . WithPrompt ( Prompt . NoPrompt ) . ExecuteAsync ( ) ;
182+ }
183+ }
184+ catch
185+ {
186+ // Unexpected exception
187+ // TODO: Send exception to a logger.
188+ }
189+ }
190+
191+ _account = authResult ? . Account ;
156192
157- return authResult ? . AccessToken ;
193+ return authResult ? . AccessToken ;
194+ }
195+ finally
196+ {
197+ SemaphoreSlim . Release ( ) ;
198+ }
158199 }
159200 }
160201}
0 commit comments