Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Graph.Core" Version="2.0.3" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.18.8" />
</ItemGroup>

Expand Down
89 changes: 65 additions & 24 deletions CommunityToolkit.Authentication.Msal/MsalProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,6 +18,8 @@ namespace CommunityToolkit.Authentication
/// </summary>
public class MsalProvider : BaseProvider
{
private static readonly SemaphoreSlim SemaphoreSlim = new (1);

/// <inheritdoc />
public override string CurrentAccountId => _account?.HomeAccountId?.Identifier;

Expand Down Expand Up @@ -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;

Expand All @@ -60,7 +64,23 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri
/// <inheritdoc/>
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);
}

Expand Down Expand Up @@ -119,42 +139,63 @@ public override async Task SignOutAsync()
}

/// <inheritdoc/>
public override async Task<string> GetTokenAsync(bool silentOnly = false)
public override Task<string> GetTokenAsync(bool silentOnly = false)
{
AuthenticationResult authResult = null;
return this.GetTokenWithScopesAsync(Scopes, silentOnly);
}

private async Task<string> 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();
}
}
}
}
30 changes: 20 additions & 10 deletions CommunityToolkit.Authentication.Uwp/WindowsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,6 +29,8 @@ public class WindowsProvider : BaseProvider
/// </summary>
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";
Expand Down Expand Up @@ -181,18 +184,22 @@ public override async Task SignOutAsync()
/// <inheritdoc />
public override async Task<string> 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)
Expand All @@ -204,7 +211,7 @@ public override async Task<string> GetTokenAsync(bool silentOnly = false)
}

// Attempt to authenticate interactively.
authResult = await AuthenticateInteractiveAsync(_scopes);
authResult = await AuthenticateInteractiveAsync(scopes);
}

if (authResult?.ResponseStatus == WebTokenRequestStatus.Success)
Expand Down Expand Up @@ -232,9 +239,12 @@ public override async Task<string> GetTokenAsync(bool silentOnly = false)
catch (Exception e)
{
// TODO: Log failure
System.Diagnostics.Debug.WriteLine(e.Message);
throw e;
}
finally
{
SemaphoreSlim.Release();
}
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ private async void LoadData()
else
{
LoadDefaultImage();

if (!string.IsNullOrWhiteSpace(UserId) || !string.IsNullOrWhiteSpace(PersonQuery))
{
PersonDetails = null;
}
}
}

Expand Down