diff --git a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj index 050e3e8..7bfb3bd 100644 --- a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj +++ b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj @@ -14,6 +14,7 @@ + diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index b871901..419abdb 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -6,7 +6,9 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; +using System.Threading; using System.Threading.Tasks; +using Microsoft.Graph; using Microsoft.Identity.Client; namespace CommunityToolkit.Authentication @@ -16,6 +18,8 @@ namespace CommunityToolkit.Authentication /// public class MsalProvider : BaseProvider { + private static readonly SemaphoreSlim SemaphoreSlim = new (1); + /// public override string CurrentAccountId => _account?.HomeAccountId?.Identifier; @@ -47,7 +51,7 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString()) .Build(); - Scopes = scopes ?? new string[] { string.Empty }; + Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }; Client = client; @@ -60,7 +64,23 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri /// public override async Task AuthenticateRequestAsync(HttpRequestMessage request) { - string token = await GetTokenAsync(); + string token; + + // Check if any specific scopes are being requested. + if (request.Properties.TryGetValue(nameof(GraphRequestContext), out object requestContextObj) && + requestContextObj is GraphRequestContext requestContext && + requestContext.MiddlewareOptions.TryGetValue(nameof(AuthenticationHandlerOption), out IMiddlewareOption optionsMiddleware) && + optionsMiddleware is AuthenticationHandlerOption options && + options.AuthenticationProviderOption?.Scopes != null && options.AuthenticationProviderOption.Scopes.Length > 0) + { + var withScopes = options.AuthenticationProviderOption.Scopes; + token = await this.GetTokenWithScopesAsync(withScopes); + } + else + { + token = await this.GetTokenAsync(); + } + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } @@ -119,42 +139,63 @@ public override async Task SignOutAsync() } /// - public override async Task GetTokenAsync(bool silentOnly = false) + public override Task GetTokenAsync(bool silentOnly = false) { - AuthenticationResult authResult = null; + return this.GetTokenWithScopesAsync(Scopes, silentOnly); + } + + private async Task GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false) + { + await SemaphoreSlim.WaitAsync(); + try { - var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault(); - if (account != null) + AuthenticationResult authResult = null; + try { - authResult = await Client.AcquireTokenSilent(Scopes, account).ExecuteAsync(); + var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault(); + if (account != null) + { + authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync(); + } } - } - catch (MsalUiRequiredException) - { - } - catch - { - // Unexpected exception - // TODO: Send exception to a logger. - } - - if (authResult == null && !silentOnly) - { - try + catch (MsalUiRequiredException) { - authResult = await Client.AcquireTokenInteractive(Scopes).WithPrompt(Prompt.SelectAccount).ExecuteAsync(); } catch { // Unexpected exception // TODO: Send exception to a logger. } - } - _account = authResult?.Account; + if (authResult == null && !silentOnly) + { + try + { + if (_account != null) + { + authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).WithAccount(_account).ExecuteAsync(); + } + else + { + authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync(); + } + } + catch + { + // Unexpected exception + // TODO: Send exception to a logger. + } + } + + _account = authResult?.Account; - return authResult?.AccessToken; + return authResult?.AccessToken; + } + finally + { + SemaphoreSlim.Release(); + } } } } diff --git a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs index a7864c8..223a443 100644 --- a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs +++ b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using Windows.Networking.Connectivity; using Windows.Security.Authentication.Web; @@ -28,6 +29,8 @@ public class WindowsProvider : BaseProvider /// public static string RedirectUri => string.Format("ms-appx-web://Microsoft.AAD.BrokerPlugIn/{0}", WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper()); + private static readonly SemaphoreSlim SemaphoreSlim = new(1); + private const string AuthenticationHeaderScheme = "Bearer"; private const string GraphResourcePropertyKey = "resource"; private const string GraphResourcePropertyValue = "https://graph.microsoft.com"; @@ -181,18 +184,22 @@ public override async Task SignOutAsync() /// public override async Task GetTokenAsync(bool silentOnly = false) { - var internetConnectionProfile = NetworkInformation.GetInternetConnectionProfile(); - if (internetConnectionProfile == null) - { - // We are not online, no token for you. - // TODO: Is there anything special to do when we go offline? - return null; - } + await SemaphoreSlim.WaitAsync(); try { + var internetConnectionProfile = NetworkInformation.GetInternetConnectionProfile(); + if (internetConnectionProfile == null) + { + // We are not online, no token for you. + // TODO: Is there anything special to do when we go offline? + return null; + } + + var scopes = _scopes; + // Attempt to authenticate silently. - var authResult = await AuthenticateSilentAsync(_scopes); + var authResult = await AuthenticateSilentAsync(scopes); // Authenticate with user interaction as appropriate. if (authResult?.ResponseStatus != WebTokenRequestStatus.Success) @@ -204,7 +211,7 @@ public override async Task GetTokenAsync(bool silentOnly = false) } // Attempt to authenticate interactively. - authResult = await AuthenticateInteractiveAsync(_scopes); + authResult = await AuthenticateInteractiveAsync(scopes); } if (authResult?.ResponseStatus == WebTokenRequestStatus.Success) @@ -232,9 +239,12 @@ public override async Task GetTokenAsync(bool silentOnly = false) catch (Exception e) { // TODO: Log failure - System.Diagnostics.Debug.WriteLine(e.Message); throw e; } + finally + { + SemaphoreSlim.Release(); + } } /// diff --git a/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs index 8771965..5f3deee 100644 --- a/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs +++ b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs @@ -131,6 +131,11 @@ private async void LoadData() else { LoadDefaultImage(); + + if (!string.IsNullOrWhiteSpace(UserId) || !string.IsNullOrWhiteSpace(PersonQuery)) + { + PersonDetails = null; + } } }