From c1cfd035725d1608a6eccf103dc9d69b2f9e9af6 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Tue, 12 Oct 2021 15:10:48 -0700 Subject: [PATCH 01/12] Updated MsalProvider to use local account broker when possible. --- ...ommunityToolkit.Authentication.Msal.csproj | 4 +- .../MsalProvider.cs | 138 +++++++++++++++--- .../PublicClientApplicationConfig.cs | 96 ++++++++++++ .../Extensions/GraphExtensions.Users.cs | 2 +- Samples/WpfMsalProviderSample/App.xaml.cs | 5 +- .../WpfMsalProviderSample.csproj | 2 +- 6 files changed, 216 insertions(+), 31 deletions(-) create mode 100644 CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs diff --git a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj index 6ae6da7..cc5ea7a 100644 --- a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj +++ b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj @@ -1,7 +1,7 @@ - + - netstandard2.0 + netstandard2.0;uap10.0 Windows Community Toolkit .NET Standard Auth Services diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index 419abdb..4eb2f04 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -2,15 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Linq; 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; +#if WINDOWS_UWP +using Windows.Security.Authentication.Web; +#else +using System.Diagnostics; +using Microsoft.Identity.Client.Extensions.Msal; +#endif + namespace CommunityToolkit.Authentication { /// @@ -18,11 +25,20 @@ namespace CommunityToolkit.Authentication /// public class MsalProvider : BaseProvider { + public static readonly string RedirectUriPrefix = "ms-appx-web://microsoft.aad.brokerplugin/"; + private static readonly SemaphoreSlim SemaphoreSlim = new (1); + private IAccount _account; + /// public override string CurrentAccountId => _account?.HomeAccountId?.Identifier; + /// + /// Gets the configuration values for creating the instance. + /// + protected PublicClientApplicationConfig Config { get; private set; } + /// /// Gets the MSAL.NET Client used to authenticate the user. /// @@ -31,34 +47,40 @@ public class MsalProvider : BaseProvider /// /// Gets an array of scopes to use for accessing Graph resources. /// - protected string[] Scopes { get; private set; } + protected string[] Scopes => Config.Scopes; - private IAccount _account; + /// + /// Initializes a new instance of the class using a configuration object. + /// + /// Configuration values for building the instance. + /// Determines whether the provider attempts to silently log in upon creation. + public MsalProvider(PublicClientApplicationConfig config, bool autoSignIn = true) + { + Config = config; + Client = CreatePublicClientApplication(config); + + InitTokenCacheAsync(autoSignIn); + } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class with default configuration values. /// /// Registered ClientId. /// RedirectUri for auth response. /// List of Scopes to initially request. - /// Determines whether the provider attempts to silently log in upon instantionation. - public MsalProvider(string clientId, string[] scopes = null, string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", bool autoSignIn = true) + /// Determines whether the provider attempts to silently log in upon creation. + public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true) { - var client = PublicClientApplicationBuilder.Create(clientId) - .WithAuthority(AzureCloudInstance.AzurePublic, AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount) - .WithRedirectUri(redirectUri) - .WithClientName(ProviderManager.ClientName) - .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString()) - .Build(); - - Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }; + Config = new PublicClientApplicationConfig() + { + ClientId = clientId, + Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }, + RedirectUri = redirectUri, + }; - Client = client; + Client = CreatePublicClientApplication(Config); - if (autoSignIn) - { - _ = TrySilentSignInAsync(); - } + InitTokenCacheAsync(autoSignIn); } /// @@ -144,6 +166,62 @@ public override Task GetTokenAsync(bool silentOnly = false) return this.GetTokenWithScopesAsync(Scopes, silentOnly); } + private static IPublicClientApplication CreatePublicClientApplication(PublicClientApplicationConfig config) + { + var clientBuilder = PublicClientApplicationBuilder.Create(config.ClientId) + .WithAuthority(AzureCloudInstance.AzurePublic, config.Authority) + .WithClientName(config.ClientName) + .WithClientVersion(config.ClientVersion); + +#if WINDOWS_UWP + clientBuilder = clientBuilder + .WithBroker() + .WithWindowsBrokerOptions(new WindowsBrokerOptions() + { + ListWindowsWorkAndSchoolAccounts = true, + }); +#endif + + if (config.RedirectUri == null) + { +#if WINDOWS_UWP + string sid = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper(); + config.RedirectUri = $"{RedirectUriPrefix}{sid}"; +#else + config.RedirectUri = "http://localhost"; + // config.RedirectUri = $"{RedirectUriPrefix}{config.ClientId}"; +#endif + } + + return clientBuilder.WithRedirectUri(config.RedirectUri).Build(); + } + + private async Task InitTokenCacheAsync(bool trySignIn) + { +#if !WINDOWS_UWP + // Token cache persistence (not required on UWP as MSAL does it for you) + var storageProperties = new StorageCreationPropertiesBuilder(Config.CacheFileName, Config.CacheDir) + .WithLinuxKeyring( + Config.LinuxKeyRingSchema, + Config.LinuxKeyRingCollection, + Config.LinuxKeyRingLabel, + Config.LinuxKeyRingAttr1, + Config.LinuxKeyRingAttr2) + .WithMacKeyChain( + Config.KeyChainServiceName, + Config.KeyChainAccountName) + .Build(); + + var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties); + cacheHelper.RegisterCache(Client.UserTokenCache); +#endif + + if (trySignIn) + { + _ = TrySilentSignInAsync(); + } + } + private async Task GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false) { await SemaphoreSlim.WaitAsync(); @@ -172,14 +250,26 @@ private async Task GetTokenWithScopesAsync(string[] scopes, bool silentO { try { + var paramBuilder = Client.AcquireTokenInteractive(scopes); + if (_account != null) { - authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).WithAccount(_account).ExecuteAsync(); - } - else - { - authResult = await Client.AcquireTokenInteractive(scopes).WithPrompt(Prompt.NoPrompt).ExecuteAsync(); + paramBuilder = paramBuilder.WithAccount(_account); } + +#if WINDOWS_UWP + // For UWP, specify NoPrompt for the least intrusive user experience. + paramBuilder = paramBuilder.WithPrompt(Prompt.NoPrompt); +#else + // Otherwise, get the process by FriendlyName from Application Domain + var friendlyName = AppDomain.CurrentDomain.FriendlyName; + var proc = Process.GetProcessesByName(friendlyName).First(); + + var windowHandle = proc.MainWindowHandle; + paramBuilder = paramBuilder.WithParentActivityOrWindow(windowHandle); +#endif + + authResult = await paramBuilder.ExecuteAsync(); } catch { diff --git a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs new file mode 100644 index 0000000..1e3cda8 --- /dev/null +++ b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Extensions.Msal; + +namespace CommunityToolkit.Authentication +{ + /// + /// Configuration values for building a new instance of the object. + /// + public class PublicClientApplicationConfig + { + // App settings + + /// + /// Gets or sets the authority used to control which types of accounts can login. + /// + public AadAuthorityAudience Authority { get; set; } = AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount; + + /// + /// Gets or sets the client id value from the Azure app registration. + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the client name value. + /// + public string ClientName { get; set; } = ProviderManager.ClientName; + + /// + /// Gets or sets the client version value. + /// + public string ClientVersion { get; set; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + + /// + /// Gets or sets the redirect uri value used to complete authentication. + /// + public string RedirectUri { get; set; } + + /// + /// Gets or sets the array of authorization scopes. + /// + public string[] Scopes { get; set; } + + // Cache settings + + /// + /// Gets or sets the name of the cache file. + /// + public string CacheFileName { get; set; } = "msal_cache.dat"; + + /// + /// Gets or sets the directory of the cache file. + /// + public string CacheDir { get; set; } = "MSAL_CACHE"; + + /// + /// Gets or sets the key chain service name. + /// + public string KeyChainServiceName { get; set; } = "msal_service"; + + /// + /// Gets or sets the key chain account name. + /// + public string KeyChainAccountName { get; set; } = "msal_account"; + + /// + /// Gets or sets the key ring schema on linux. + /// + public string LinuxKeyRingSchema { get; set; } = "com.msal.wct.tokencache"; + + /// + /// Gets or sets the key ring collection type on linux. + /// + public string LinuxKeyRingCollection { get; set; } = MsalCacheHelper.LinuxKeyRingDefaultCollection; + + /// + /// Gets or sets the key ring label on linux. + /// + public string LinuxKeyRingLabel { get; set; } = "Default MSAL token cache for all Windows Community Toolkit based apps."; + + /// + /// Gets or sets the first key ring attribute on linux. + /// + public KeyValuePair LinuxKeyRingAttr1 { get; set; } = new KeyValuePair("Version", "1"); + + /// + /// Gets or sets the second key ring attribute on linux. + /// + public KeyValuePair LinuxKeyRingAttr2 { get; set; } = new KeyValuePair("ProductGroup", "MyApps"); + + // For Username / Password flow - to be used only for testing! + // public const string Username = ""; + // public const string Password = ""; + } +} diff --git a/CommunityToolkit.Graph/Extensions/GraphExtensions.Users.cs b/CommunityToolkit.Graph/Extensions/GraphExtensions.Users.cs index 98d82af..087e998 100644 --- a/CommunityToolkit.Graph/Extensions/GraphExtensions.Users.cs +++ b/CommunityToolkit.Graph/Extensions/GraphExtensions.Users.cs @@ -71,7 +71,7 @@ public static async Task GetUserPhoto(this GraphServiceClient graph, str .Photo .Content .Request() - .WithScopes(new string[] { "user.readbasic.all" }) + .WithScopes(new string[] { "user.read" }) .GetAsync(); } diff --git a/Samples/WpfMsalProviderSample/App.xaml.cs b/Samples/WpfMsalProviderSample/App.xaml.cs index d8a33ce..23bacd2 100644 --- a/Samples/WpfMsalProviderSample/App.xaml.cs +++ b/Samples/WpfMsalProviderSample/App.xaml.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using CommunityToolkit.Authentication; using System; using System.Windows; +using CommunityToolkit.Authentication; namespace WpfMsalProviderSample { @@ -16,8 +16,7 @@ protected override void OnActivated(EventArgs e) { string clientId = "YOUR-CLIENT-ID-HERE"; string[] scopes = new string[] { "User.Read" }; - string redirectUri = "http://localhost"; - ProviderManager.Instance.GlobalProvider = new MsalProvider(clientId, scopes, redirectUri); + ProviderManager.Instance.GlobalProvider = new MsalProvider(clientId, scopes); } base.OnActivated(e); diff --git a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj index a5046f0..42d55f7 100644 --- a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj +++ b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp3.1 + net5.0-windows true From da28c50426137a47ba6a2b9db7357d785c2dc216 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Tue, 12 Oct 2021 16:13:05 -0700 Subject: [PATCH 02/12] Added license header --- .../PublicClientApplicationConfig.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs index 1e3cda8..ba5a7f3 100644 --- a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs +++ b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using System.Reflection; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; From b13c0bfbde251aa1843c09c5857e77cef7df228a Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Tue, 12 Oct 2021 16:47:11 -0700 Subject: [PATCH 03/12] Fixed bad reference from netstandard2.0 to 1.4 in auth project --- .../BaseProvider.cs | 12 ----------- .../CommunityToolkit.Authentication.csproj | 2 +- .../HttpRequestMessageExtensions.cs | 20 ------------------- .../MockProvider.cs | 3 --- .../WpfMsalProviderSample.csproj | 2 +- 5 files changed, 2 insertions(+), 37 deletions(-) diff --git a/CommunityToolkit.Authentication/BaseProvider.cs b/CommunityToolkit.Authentication/BaseProvider.cs index cbeaeb6..7f37c33 100644 --- a/CommunityToolkit.Authentication/BaseProvider.cs +++ b/CommunityToolkit.Authentication/BaseProvider.cs @@ -5,7 +5,6 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using CommunityToolkit.Authentication.Extensions; namespace CommunityToolkit.Authentication { @@ -62,16 +61,5 @@ public BaseProvider() /// public abstract Task TrySilentSignInAsync(); - - /// - /// Append the Sdk version to the request headers. - /// - /// - /// The request to append the header to. - /// - protected void AddSdkVersion(HttpRequestMessage request) - { - request.AddSdkVersion(); - } } } diff --git a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj index cd25959..4d89c17 100644 --- a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj +++ b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard1.4 Windows Community Toolkit .NET Standard Auth Services diff --git a/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs b/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs index 1acf78e..8b673d1 100644 --- a/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs +++ b/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs @@ -3,10 +3,8 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; -using System.Reflection; using System.Threading.Tasks; namespace CommunityToolkit.Authentication.Extensions @@ -16,27 +14,9 @@ namespace CommunityToolkit.Authentication.Extensions /// public static class HttpRequestMessageExtensions { - private const string SdkVersion = "SdkVersion"; - private const string LibraryName = "wct"; private const string Bearer = "Bearer"; private const string MockGraphToken = "{token:https://graph.microsoft.com/}"; - internal static void AddSdkVersion(this HttpRequestMessage request) - { - if (request == null || request.Headers == null) - { - return; - } - - if (request.Headers.TryGetValues(SdkVersion, out IEnumerable values)) - { - var versions = new List(values); - versions.Insert(0, LibraryName + "/" + Assembly.GetExecutingAssembly().GetName().Version); - request.Headers.Remove(SdkVersion); - request.Headers.Add(SdkVersion, versions); - } - } - internal static void AddMockProviderToken(this HttpRequestMessage request) { request diff --git a/CommunityToolkit.Authentication/MockProvider.cs b/CommunityToolkit.Authentication/MockProvider.cs index d66e63b..c6e9223 100644 --- a/CommunityToolkit.Authentication/MockProvider.cs +++ b/CommunityToolkit.Authentication/MockProvider.cs @@ -38,9 +38,6 @@ public MockProvider(bool signedIn = true) /// public override async Task AuthenticateRequestAsync(HttpRequestMessage request) { - // Append the SDK version header - AddSdkVersion(request); - // Append the token auth header request.AddMockProviderToken(); diff --git a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj index 42d55f7..695a650 100644 --- a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj +++ b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj @@ -1,4 +1,4 @@ - + WinExe From 5798591d6a5d63003ac5d2c5a6bbefa2b0c49f60 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 14 Oct 2021 12:16:38 -0700 Subject: [PATCH 04/12] Updated MsalProvider configuration to support toggling org account login. --- .../MsalProvider.cs | 58 +++++++++++++------ .../PublicClientApplicationConfig.cs | 7 ++- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index 4eb2f04..9a70515 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -25,14 +25,20 @@ namespace CommunityToolkit.Authentication /// public class MsalProvider : BaseProvider { - public static readonly string RedirectUriPrefix = "ms-appx-web://microsoft.aad.brokerplugin/"; + /// + /// A prefix value used to create the redirect URI value for use in AAD. + /// + public static readonly string MSAccountBrokerRedirectUriPrefix = "ms-appx-web://microsoft.aad.brokerplugin/"; private static readonly SemaphoreSlim SemaphoreSlim = new (1); - private IAccount _account; + /// + /// Gets or sets the currently authenticated user account. + /// + public IAccount Account { get; protected set; } /// - public override string CurrentAccountId => _account?.HomeAccountId?.Identifier; + public override string CurrentAccountId => Account?.HomeAccountId?.Identifier; /// /// Gets the configuration values for creating the instance. @@ -109,7 +115,7 @@ optionsMiddleware is AuthenticationHandlerOption options && /// public override async Task TrySilentSignInAsync() { - if (_account != null && State == ProviderState.SignedIn) + if (Account != null && State == ProviderState.SignedIn) { return true; } @@ -130,7 +136,7 @@ public override async Task TrySilentSignInAsync() /// public override async Task SignInAsync() { - if (_account != null || State != ProviderState.SignedOut) + if (Account != null || State != ProviderState.SignedOut) { return; } @@ -151,10 +157,10 @@ public override async Task SignInAsync() /// public override async Task SignOutAsync() { - if (_account != null) + if (Account != null) { - await Client.RemoveAsync(_account); - _account = null; + await Client.RemoveAsync(Account); + Account = null; } State = ProviderState.SignedOut; @@ -166,7 +172,12 @@ public override Task GetTokenAsync(bool silentOnly = false) return this.GetTokenWithScopesAsync(Scopes, silentOnly); } - private static IPublicClientApplication CreatePublicClientApplication(PublicClientApplicationConfig config) + /// + /// Create an instance of using the provided config. + /// + /// An set of properties used to configure the creation. + /// A new instance of . + protected IPublicClientApplication CreatePublicClientApplication(PublicClientApplicationConfig config) { var clientBuilder = PublicClientApplicationBuilder.Create(config.ClientId) .WithAuthority(AzureCloudInstance.AzurePublic, config.Authority) @@ -178,7 +189,7 @@ private static IPublicClientApplication CreatePublicClientApplication(PublicClie .WithBroker() .WithWindowsBrokerOptions(new WindowsBrokerOptions() { - ListWindowsWorkAndSchoolAccounts = true, + ListWindowsWorkAndSchoolAccounts = config.ListWindowsWorkAndSchoolAccounts, }); #endif @@ -186,17 +197,22 @@ private static IPublicClientApplication CreatePublicClientApplication(PublicClie { #if WINDOWS_UWP string sid = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper(); - config.RedirectUri = $"{RedirectUriPrefix}{sid}"; + config.RedirectUri = $"{MSAccountBrokerRedirectUriPrefix}{sid}"; #else config.RedirectUri = "http://localhost"; - // config.RedirectUri = $"{RedirectUriPrefix}{config.ClientId}"; + // config.RedirectUri = $"{MSAccountBrokerRedirectUriPrefix}{config.ClientId}"; #endif } return clientBuilder.WithRedirectUri(config.RedirectUri).Build(); } - private async Task InitTokenCacheAsync(bool trySignIn) + /// + /// Initialize the token cache and if requested, attempt to sign in silently. + /// + /// A value indicating whether to attempt silent login after the cache configuration. + /// A representing the result of the asynchronous operation. + protected async Task InitTokenCacheAsync(bool trySignIn) { #if !WINDOWS_UWP // Token cache persistence (not required on UWP as MSAL does it for you) @@ -222,7 +238,13 @@ private async Task InitTokenCacheAsync(bool trySignIn) } } - private async Task GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false) + /// + /// Retrieve an authorization token using the provided scopes. + /// + /// An array of scopes to pass along with the Graph request. + /// A value to determine whether account broker UI should be shown, if required by MSAL. + /// A representing the result of the asynchronous operation. + protected async Task GetTokenWithScopesAsync(string[] scopes, bool silentOnly = false) { await SemaphoreSlim.WaitAsync(); @@ -231,7 +253,7 @@ private async Task GetTokenWithScopesAsync(string[] scopes, bool silentO AuthenticationResult authResult = null; try { - var account = _account ?? (await Client.GetAccountsAsync()).FirstOrDefault(); + var account = Account ?? (await Client.GetAccountsAsync()).FirstOrDefault(); if (account != null) { authResult = await Client.AcquireTokenSilent(scopes, account).ExecuteAsync(); @@ -252,9 +274,9 @@ private async Task GetTokenWithScopesAsync(string[] scopes, bool silentO { var paramBuilder = Client.AcquireTokenInteractive(scopes); - if (_account != null) + if (Account != null) { - paramBuilder = paramBuilder.WithAccount(_account); + paramBuilder = paramBuilder.WithAccount(Account); } #if WINDOWS_UWP @@ -278,7 +300,7 @@ private async Task GetTokenWithScopesAsync(string[] scopes, bool silentO } } - _account = authResult?.Account; + Account = authResult?.Account; return authResult?.AccessToken; } diff --git a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs index ba5a7f3..2d23067 100644 --- a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs +++ b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs @@ -93,8 +93,9 @@ public class PublicClientApplicationConfig /// public KeyValuePair LinuxKeyRingAttr2 { get; set; } = new KeyValuePair("ProductGroup", "MyApps"); - // For Username / Password flow - to be used only for testing! - // public const string Username = ""; - // public const string Password = ""; + /// + /// Gets or sets a value indicating whether the user should be able to login with organizational accounts or not. + /// + public bool ListWindowsWorkAndSchoolAccounts { get; set; } = true; } } From 9324b8b5a19aa10f831a37da3903da3df6a59e51 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 14 Oct 2021 13:20:17 -0700 Subject: [PATCH 05/12] Reverting CT.Auth package to netstandard2.0 and fixing min version ref in CT.Auth.Msal package --- .../CommunityToolkit.Authentication.Msal.csproj | 1 + .../CommunityToolkit.Authentication.csproj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj index cc5ea7a..282d9e1 100644 --- a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj +++ b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj @@ -2,6 +2,7 @@ netstandard2.0;uap10.0 + 10.0.16299.0 Windows Community Toolkit .NET Standard Auth Services diff --git a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj index 4d89c17..cd25959 100644 --- a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj +++ b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj @@ -1,6 +1,6 @@  - netstandard1.4 + netstandard2.0 Windows Community Toolkit .NET Standard Auth Services From 971bb25ed36cb989b70183999f19343f45e64085 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Wed, 20 Oct 2021 13:19:18 -0700 Subject: [PATCH 06/12] Re-adding SDK version header logic --- .../MsalProvider.cs | 3 ++- .../WindowsProvider.cs | 2 ++ .../BaseProvider.cs | 12 +++++++++++ .../HttpRequestMessageExtensions.cs | 20 +++++++++++++++++++ .../MockProvider.cs | 3 +++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index 9a70515..ac1141e 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -92,6 +92,8 @@ public MsalProvider(string clientId, string[] scopes = null, string redirectUri /// public override async Task AuthenticateRequestAsync(HttpRequestMessage request) { + AddSdkVersion(request); + string token; // Check if any specific scopes are being requested. @@ -200,7 +202,6 @@ protected IPublicClientApplication CreatePublicClientApplication(PublicClientApp config.RedirectUri = $"{MSAccountBrokerRedirectUriPrefix}{sid}"; #else config.RedirectUri = "http://localhost"; - // config.RedirectUri = $"{MSAccountBrokerRedirectUriPrefix}{config.ClientId}"; #endif } diff --git a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs index 914e259..57bc2ea 100644 --- a/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs +++ b/CommunityToolkit.Authentication.Uwp/WindowsProvider.cs @@ -110,6 +110,8 @@ public WindowsProvider(string[] scopes = null, WebAccountProviderConfig? webAcco /// public override async Task AuthenticateRequestAsync(HttpRequestMessage request) { + AddSdkVersion(request); + string token = await GetTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderScheme, token); } diff --git a/CommunityToolkit.Authentication/BaseProvider.cs b/CommunityToolkit.Authentication/BaseProvider.cs index 7f37c33..cbeaeb6 100644 --- a/CommunityToolkit.Authentication/BaseProvider.cs +++ b/CommunityToolkit.Authentication/BaseProvider.cs @@ -5,6 +5,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using CommunityToolkit.Authentication.Extensions; namespace CommunityToolkit.Authentication { @@ -61,5 +62,16 @@ public BaseProvider() /// public abstract Task TrySilentSignInAsync(); + + /// + /// Append the Sdk version to the request headers. + /// + /// + /// The request to append the header to. + /// + protected void AddSdkVersion(HttpRequestMessage request) + { + request.AddSdkVersion(); + } } } diff --git a/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs b/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs index 8b673d1..1acf78e 100644 --- a/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs +++ b/CommunityToolkit.Authentication/Extensions/HttpRequestMessageExtensions.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Threading.Tasks; namespace CommunityToolkit.Authentication.Extensions @@ -14,9 +16,27 @@ namespace CommunityToolkit.Authentication.Extensions /// public static class HttpRequestMessageExtensions { + private const string SdkVersion = "SdkVersion"; + private const string LibraryName = "wct"; private const string Bearer = "Bearer"; private const string MockGraphToken = "{token:https://graph.microsoft.com/}"; + internal static void AddSdkVersion(this HttpRequestMessage request) + { + if (request == null || request.Headers == null) + { + return; + } + + if (request.Headers.TryGetValues(SdkVersion, out IEnumerable values)) + { + var versions = new List(values); + versions.Insert(0, LibraryName + "/" + Assembly.GetExecutingAssembly().GetName().Version); + request.Headers.Remove(SdkVersion); + request.Headers.Add(SdkVersion, versions); + } + } + internal static void AddMockProviderToken(this HttpRequestMessage request) { request diff --git a/CommunityToolkit.Authentication/MockProvider.cs b/CommunityToolkit.Authentication/MockProvider.cs index c6e9223..d66e63b 100644 --- a/CommunityToolkit.Authentication/MockProvider.cs +++ b/CommunityToolkit.Authentication/MockProvider.cs @@ -38,6 +38,9 @@ public MockProvider(bool signedIn = true) /// public override async Task AuthenticateRequestAsync(HttpRequestMessage request) { + // Append the SDK version header + AddSdkVersion(request); + // Append the token auth header request.AddMockProviderToken(); From c3fbd6db4fcbaf23b9a23557ec851d2e7645d986 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Fri, 22 Oct 2021 10:35:49 -0700 Subject: [PATCH 07/12] Removed PublicClientApplicationConfig --- .../MsalProvider.cs | 120 +++++++----------- .../MsalProviderExtensions.cs | 35 +++++ .../PublicClientApplicationConfig.cs | 101 --------------- 3 files changed, 78 insertions(+), 178 deletions(-) create mode 100644 CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs delete mode 100644 CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index ac1141e..f190bf1 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Graph; @@ -15,7 +16,6 @@ using Windows.Security.Authentication.Web; #else using System.Diagnostics; -using Microsoft.Identity.Client.Extensions.Msal; #endif namespace CommunityToolkit.Authentication @@ -41,52 +41,50 @@ public class MsalProvider : BaseProvider public override string CurrentAccountId => Account?.HomeAccountId?.Identifier; /// - /// Gets the configuration values for creating the instance. + /// Gets or sets the MSAL.NET Client used to authenticate the user. /// - protected PublicClientApplicationConfig Config { get; private set; } - - /// - /// Gets the MSAL.NET Client used to authenticate the user. - /// - protected IPublicClientApplication Client { get; private set; } + public IPublicClientApplication Client { get; protected set; } /// /// Gets an array of scopes to use for accessing Graph resources. /// - protected string[] Scopes => Config.Scopes; + protected string[] Scopes { get; } /// /// Initializes a new instance of the class using a configuration object. /// - /// Configuration values for building the instance. + /// Registered ClientId in Azure Acitve Directory. + /// List of Scopes to initially request. /// Determines whether the provider attempts to silently log in upon creation. - public MsalProvider(PublicClientApplicationConfig config, bool autoSignIn = true) + public MsalProvider(IPublicClientApplication client, string[] scopes = null, bool autoSignIn = true) { - Config = config; - Client = CreatePublicClientApplication(config); + Client = client; + Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }; - InitTokenCacheAsync(autoSignIn); + if (autoSignIn) + { + TrySilentSignInAsync(); + } } /// /// Initializes a new instance of the class with default configuration values. /// - /// Registered ClientId. + /// Registered client id in Azure Acitve Directory. /// RedirectUri for auth response. /// List of Scopes to initially request. /// Determines whether the provider attempts to silently log in upon creation. - public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true) + /// Determines if organizational accounts should be enabled/disabled. + /// Registered tenant id in Azure Active Directory. + public MsalProvider(string clientId, string[] scopes = null, string redirectUri = null, bool autoSignIn = true, bool listWindowsWorkAndSchoolAccounts = true, string tenantId = null) { - Config = new PublicClientApplicationConfig() - { - ClientId = clientId, - Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }, - RedirectUri = redirectUri, - }; - - Client = CreatePublicClientApplication(Config); + Client = CreatePublicClientApplication(clientId, tenantId, redirectUri, listWindowsWorkAndSchoolAccounts); + Scopes = scopes.Select(s => s.ToLower()).ToArray() ?? new string[] { string.Empty }; - InitTokenCacheAsync(autoSignIn); + if (autoSignIn) + { + TrySilentSignInAsync(); + } } /// @@ -175,68 +173,36 @@ public override Task GetTokenAsync(bool silentOnly = false) } /// - /// Create an instance of using the provided config. + /// Create an instance of using the provided config and some default values. /// - /// An set of properties used to configure the creation. + /// Registered ClientId. + /// An optional tenant id. + /// Redirect uri for auth response. + /// Determines if organizational accounts should be supported. /// A new instance of . - protected IPublicClientApplication CreatePublicClientApplication(PublicClientApplicationConfig config) + protected IPublicClientApplication CreatePublicClientApplication(string clientId, string tenantId, string redirectUri, bool listWindowsWorkAndSchoolAccounts) { - var clientBuilder = PublicClientApplicationBuilder.Create(config.ClientId) - .WithAuthority(AzureCloudInstance.AzurePublic, config.Authority) - .WithClientName(config.ClientName) - .WithClientVersion(config.ClientVersion); + var authority = listWindowsWorkAndSchoolAccounts ? AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount : AadAuthorityAudience.PersonalMicrosoftAccount; -#if WINDOWS_UWP - clientBuilder = clientBuilder - .WithBroker() - .WithWindowsBrokerOptions(new WindowsBrokerOptions() - { - ListWindowsWorkAndSchoolAccounts = config.ListWindowsWorkAndSchoolAccounts, - }); -#endif + var clientBuilder = PublicClientApplicationBuilder.Create(clientId) + .WithAuthority(AzureCloudInstance.AzurePublic, authority) + .WithClientName(ProviderManager.ClientName) + .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString()); - if (config.RedirectUri == null) + if (tenantId != null) { -#if WINDOWS_UWP - string sid = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper(); - config.RedirectUri = $"{MSAccountBrokerRedirectUriPrefix}{sid}"; -#else - config.RedirectUri = "http://localhost"; -#endif + clientBuilder = clientBuilder.WithTenantId(tenantId); } - return clientBuilder.WithRedirectUri(config.RedirectUri).Build(); - } - - /// - /// Initialize the token cache and if requested, attempt to sign in silently. - /// - /// A value indicating whether to attempt silent login after the cache configuration. - /// A representing the result of the asynchronous operation. - protected async Task InitTokenCacheAsync(bool trySignIn) - { -#if !WINDOWS_UWP - // Token cache persistence (not required on UWP as MSAL does it for you) - var storageProperties = new StorageCreationPropertiesBuilder(Config.CacheFileName, Config.CacheDir) - .WithLinuxKeyring( - Config.LinuxKeyRingSchema, - Config.LinuxKeyRingCollection, - Config.LinuxKeyRingLabel, - Config.LinuxKeyRingAttr1, - Config.LinuxKeyRingAttr2) - .WithMacKeyChain( - Config.KeyChainServiceName, - Config.KeyChainAccountName) - .Build(); - - var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties); - cacheHelper.RegisterCache(Client.UserTokenCache); +#if WINDOWS_UWP + clientBuilder = clientBuilder.WithBroker(); #endif - if (trySignIn) - { - _ = TrySilentSignInAsync(); - } + clientBuilder = (redirectUri != null) + ? clientBuilder.WithRedirectUri(redirectUri) + : clientBuilder.WithDefaultRedirectUri(); + + return clientBuilder.Build(); } /// diff --git a/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs b/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs new file mode 100644 index 0000000..0e60295 --- /dev/null +++ b/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Identity.Client.Extensions.Msal; + +namespace CommunityToolkit.Authentication.Msal +{ + /// + /// Helpers for working with the MsalProvider. + /// + public static class MsalProviderExtensions + { + /// + /// Helper function to initialize the token cache for non-UWP apps. MSAL handles this automatically on UWP. + /// + /// The instance of to init the cache for. + /// Properties for configuring the storage cache. + /// Passing null uses the default TraceSource logger. + /// A representing the result of the asynchronous operation. + public static async Task InitTokenCacheAsync( + this MsalProvider provider, + StorageCreationProperties storageProperties, + TraceSource logger = null) + { +#if !WINDOWS_UWP + // Token cache persistence (not required on UWP as MSAL does it for you) + var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties, logger); + cacheHelper.RegisterCache(provider.Client.UserTokenCache); +#endif + } + } +} diff --git a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs b/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs deleted file mode 100644 index 2d23067..0000000 --- a/CommunityToolkit.Authentication.Msal/PublicClientApplicationConfig.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Reflection; -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensions.Msal; - -namespace CommunityToolkit.Authentication -{ - /// - /// Configuration values for building a new instance of the object. - /// - public class PublicClientApplicationConfig - { - // App settings - - /// - /// Gets or sets the authority used to control which types of accounts can login. - /// - public AadAuthorityAudience Authority { get; set; } = AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount; - - /// - /// Gets or sets the client id value from the Azure app registration. - /// - public string ClientId { get; set; } - - /// - /// Gets or sets the client name value. - /// - public string ClientName { get; set; } = ProviderManager.ClientName; - - /// - /// Gets or sets the client version value. - /// - public string ClientVersion { get; set; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - - /// - /// Gets or sets the redirect uri value used to complete authentication. - /// - public string RedirectUri { get; set; } - - /// - /// Gets or sets the array of authorization scopes. - /// - public string[] Scopes { get; set; } - - // Cache settings - - /// - /// Gets or sets the name of the cache file. - /// - public string CacheFileName { get; set; } = "msal_cache.dat"; - - /// - /// Gets or sets the directory of the cache file. - /// - public string CacheDir { get; set; } = "MSAL_CACHE"; - - /// - /// Gets or sets the key chain service name. - /// - public string KeyChainServiceName { get; set; } = "msal_service"; - - /// - /// Gets or sets the key chain account name. - /// - public string KeyChainAccountName { get; set; } = "msal_account"; - - /// - /// Gets or sets the key ring schema on linux. - /// - public string LinuxKeyRingSchema { get; set; } = "com.msal.wct.tokencache"; - - /// - /// Gets or sets the key ring collection type on linux. - /// - public string LinuxKeyRingCollection { get; set; } = MsalCacheHelper.LinuxKeyRingDefaultCollection; - - /// - /// Gets or sets the key ring label on linux. - /// - public string LinuxKeyRingLabel { get; set; } = "Default MSAL token cache for all Windows Community Toolkit based apps."; - - /// - /// Gets or sets the first key ring attribute on linux. - /// - public KeyValuePair LinuxKeyRingAttr1 { get; set; } = new KeyValuePair("Version", "1"); - - /// - /// Gets or sets the second key ring attribute on linux. - /// - public KeyValuePair LinuxKeyRingAttr2 { get; set; } = new KeyValuePair("ProductGroup", "MyApps"); - - /// - /// Gets or sets a value indicating whether the user should be able to login with organizational accounts or not. - /// - public bool ListWindowsWorkAndSchoolAccounts { get; set; } = true; - } -} From 615a0d6e0a2476248294eac6347c0b72f5fde472 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 25 Oct 2021 12:22:57 -0700 Subject: [PATCH 08/12] Added token cache example to wpf sample app. --- ...ommunityToolkit.Authentication.Msal.csproj | 7 ++-- .../MsalProvider.cs | 2 +- .../MsalProviderExtensions.cs | 8 ++--- Samples/WpfMsalProviderSample/App.xaml.cs | 36 ++++++++++++++++--- Samples/WpfMsalProviderSample/CacheConfig.cs | 23 ++++++++++++ .../WpfMsalProviderSample.csproj | 6 +++- 6 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 Samples/WpfMsalProviderSample/CacheConfig.cs diff --git a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj index 282d9e1..4cd094c 100644 --- a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj +++ b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj @@ -1,9 +1,10 @@  - netstandard2.0;uap10.0 - 10.0.16299.0 - + netstandard2.0;uap10.0;net5.0-windows10.0.17763.0 + 10.0.17763.0 + 7 + Windows Community Toolkit .NET Standard Auth Services This library provides an authentication provider based on the native Windows dialogues. It is part of the Windows Community Toolkit. diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index f190bf1..a03b618 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -194,7 +194,7 @@ protected IPublicClientApplication CreatePublicClientApplication(string clientId clientBuilder = clientBuilder.WithTenantId(tenantId); } -#if WINDOWS_UWP +#if WINDOWS_UWP || NET5_0_WINDOWS10_0_17763_0 clientBuilder = clientBuilder.WithBroker(); #endif diff --git a/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs b/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs index 0e60295..63d2930 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProviderExtensions.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Identity.Client.Extensions.Msal; -namespace CommunityToolkit.Authentication.Msal +namespace CommunityToolkit.Authentication.Extensions { /// /// Helpers for working with the MsalProvider. @@ -26,9 +26,9 @@ public static async Task InitTokenCacheAsync( TraceSource logger = null) { #if !WINDOWS_UWP - // Token cache persistence (not required on UWP as MSAL does it for you) - var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties, logger); - cacheHelper.RegisterCache(provider.Client.UserTokenCache); + // Token cache persistence (not required on UWP as MSAL does it for you) + var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties, logger); + cacheHelper.RegisterCache(provider.Client.UserTokenCache); #endif } } diff --git a/Samples/WpfMsalProviderSample/App.xaml.cs b/Samples/WpfMsalProviderSample/App.xaml.cs index 23bacd2..c964c9e 100644 --- a/Samples/WpfMsalProviderSample/App.xaml.cs +++ b/Samples/WpfMsalProviderSample/App.xaml.cs @@ -3,23 +3,49 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading.Tasks; using System.Windows; using CommunityToolkit.Authentication; +using CommunityToolkit.Authentication.Extensions; +using Microsoft.Identity.Client.Extensions.Msal; namespace WpfMsalProviderSample { public partial class App : Application { + static readonly string ClientId = "YOUR-CLIENT-ID-HERE"; + static readonly string[] Scopes = new string[] { "User.Read" }; + protected override void OnActivated(EventArgs e) + { + InitializeGlobalProviderAsync(); + base.OnActivated(e); + } + + private async Task InitializeGlobalProviderAsync() { if (ProviderManager.Instance.GlobalProvider == null) { - string clientId = "YOUR-CLIENT-ID-HERE"; - string[] scopes = new string[] { "User.Read" }; - ProviderManager.Instance.GlobalProvider = new MsalProvider(clientId, scopes); - } + var provider = new MsalProvider(ClientId, Scopes, null, false, true); - base.OnActivated(e); + // Configure the token cache storage for non-UWP applications. + var storageProperties = new StorageCreationPropertiesBuilder(CacheConfig.CacheFileName, CacheConfig.CacheDir) + .WithLinuxKeyring( + CacheConfig.LinuxKeyRingSchema, + CacheConfig.LinuxKeyRingCollection, + CacheConfig.LinuxKeyRingLabel, + CacheConfig.LinuxKeyRingAttr1, + CacheConfig.LinuxKeyRingAttr2) + .WithMacKeyChain( + CacheConfig.KeyChainServiceName, + CacheConfig.KeyChainAccountName) + .Build(); + await provider.InitTokenCacheAsync(storageProperties); + + ProviderManager.Instance.GlobalProvider = provider; + + await provider.TrySilentSignInAsync(); + } } } } diff --git a/Samples/WpfMsalProviderSample/CacheConfig.cs b/Samples/WpfMsalProviderSample/CacheConfig.cs new file mode 100644 index 0000000..c106f4a --- /dev/null +++ b/Samples/WpfMsalProviderSample/CacheConfig.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Microsoft.Identity.Client.Extensions.Msal; + +namespace WpfMsalProviderSample +{ + /// + /// https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache + /// + class CacheConfig + { + public const string CacheFileName = "msal_cache.dat"; + public const string CacheDir = "MSAL_CACHE"; + + public const string KeyChainServiceName = "msal_service"; + public const string KeyChainAccountName = "msal_account"; + + public const string LinuxKeyRingSchema = "com.contoso.devtools.tokencache"; + public const string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection; + public const string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps."; + public static readonly KeyValuePair LinuxKeyRingAttr1 = new KeyValuePair("Version", "1"); + public static readonly KeyValuePair LinuxKeyRingAttr2 = new KeyValuePair("ProductGroup", "MyApps"); + } +} diff --git a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj index 695a650..36360eb 100644 --- a/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj +++ b/Samples/WpfMsalProviderSample/WpfMsalProviderSample.csproj @@ -2,10 +2,14 @@ WinExe - net5.0-windows + net5.0-windows10.0.17763.0 true + + + + From ef6148a2a2c2475b3fee3946388755c5200a67c6 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 25 Oct 2021 12:27:06 -0700 Subject: [PATCH 09/12] Added missing file header --- Samples/WpfMsalProviderSample/CacheConfig.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Samples/WpfMsalProviderSample/CacheConfig.cs b/Samples/WpfMsalProviderSample/CacheConfig.cs index c106f4a..43820fe 100644 --- a/Samples/WpfMsalProviderSample/CacheConfig.cs +++ b/Samples/WpfMsalProviderSample/CacheConfig.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; using Microsoft.Identity.Client.Extensions.Msal; namespace WpfMsalProviderSample From 01c5f9a4f570c876d753695386a1b95ab9c6120d Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 25 Oct 2021 13:07:13 -0700 Subject: [PATCH 10/12] Renamed WpfMsalProviderSample to WpfNet5WindowsMsalProviderSample and added WpfNetCoreMsalProviderSample --- ...ommunityToolkit.Authentication.Msal.csproj | 6 +- .../MsalProvider.cs | 6 ++ SampleTest/SampleTest.csproj | 1 - .../WpfNet5WindowsMsalProviderSample/App.xaml | 9 +++ .../App.xaml.cs | 2 +- .../AssemblyInfo.cs | 0 .../CacheConfig.cs | 27 +++++++ .../LoginButton.xaml | 14 ++++ .../LoginButton.xaml.cs | 70 +++++++++++++++++++ .../MainWindow.xaml | 13 ++++ .../MainWindow.xaml.cs | 2 +- .../README.md | 0 .../WpfNet5WindowsMsalProviderSample.csproj} | 0 .../App.xaml | 4 +- .../WpfNetCoreMsalProviderSample/App.xaml.cs | 47 +++++++++++++ .../AssemblyInfo.cs | 10 +++ .../CacheConfig.cs | 2 +- .../LoginButton.xaml | 4 +- .../LoginButton.xaml.cs | 4 +- .../MainWindow.xaml | 4 +- .../MainWindow.xaml.cs | 33 +++++++++ .../WpfNetCoreMsalProviderSample.csproj | 15 ++++ Windows-Toolkit-Graph-Controls.sln | 45 +++++++++++- 23 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 Samples/WpfNet5WindowsMsalProviderSample/App.xaml rename Samples/{WpfMsalProviderSample => WpfNet5WindowsMsalProviderSample}/App.xaml.cs (97%) rename Samples/{WpfMsalProviderSample => WpfNet5WindowsMsalProviderSample}/AssemblyInfo.cs (100%) create mode 100644 Samples/WpfNet5WindowsMsalProviderSample/CacheConfig.cs create mode 100644 Samples/WpfNet5WindowsMsalProviderSample/LoginButton.xaml create mode 100644 Samples/WpfNet5WindowsMsalProviderSample/LoginButton.xaml.cs create mode 100644 Samples/WpfNet5WindowsMsalProviderSample/MainWindow.xaml rename Samples/{WpfMsalProviderSample => WpfNet5WindowsMsalProviderSample}/MainWindow.xaml.cs (96%) rename Samples/{WpfMsalProviderSample => WpfNet5WindowsMsalProviderSample}/README.md (100%) rename Samples/{WpfMsalProviderSample/WpfMsalProviderSample.csproj => WpfNet5WindowsMsalProviderSample/WpfNet5WindowsMsalProviderSample.csproj} (100%) rename Samples/{WpfMsalProviderSample => WpfNetCoreMsalProviderSample}/App.xaml (67%) create mode 100644 Samples/WpfNetCoreMsalProviderSample/App.xaml.cs create mode 100644 Samples/WpfNetCoreMsalProviderSample/AssemblyInfo.cs rename Samples/{WpfMsalProviderSample => WpfNetCoreMsalProviderSample}/CacheConfig.cs (97%) rename Samples/{WpfMsalProviderSample => WpfNetCoreMsalProviderSample}/LoginButton.xaml (80%) rename Samples/{WpfMsalProviderSample => WpfNetCoreMsalProviderSample}/LoginButton.xaml.cs (98%) rename Samples/{WpfMsalProviderSample => WpfNetCoreMsalProviderSample}/MainWindow.xaml (79%) create mode 100644 Samples/WpfNetCoreMsalProviderSample/MainWindow.xaml.cs create mode 100644 Samples/WpfNetCoreMsalProviderSample/WpfNetCoreMsalProviderSample.csproj diff --git a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj index 4cd094c..501d753 100644 --- a/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj +++ b/CommunityToolkit.Authentication.Msal/CommunityToolkit.Authentication.Msal.csproj @@ -1,7 +1,7 @@  - netstandard2.0;uap10.0;net5.0-windows10.0.17763.0 + netstandard2.0;uap10.0;net5.0-windows10.0.17763.0;netcoreapp3.1 10.0.17763.0 7 @@ -20,6 +20,10 @@ + + + + diff --git a/CommunityToolkit.Authentication.Msal/MsalProvider.cs b/CommunityToolkit.Authentication.Msal/MsalProvider.cs index a03b618..9f41e58 100644 --- a/CommunityToolkit.Authentication.Msal/MsalProvider.cs +++ b/CommunityToolkit.Authentication.Msal/MsalProvider.cs @@ -18,6 +18,10 @@ using System.Diagnostics; #endif +#if NETCOREAPP3_1 +using Microsoft.Identity.Client.Desktop; +#endif + namespace CommunityToolkit.Authentication { /// @@ -196,6 +200,8 @@ protected IPublicClientApplication CreatePublicClientApplication(string clientId #if WINDOWS_UWP || NET5_0_WINDOWS10_0_17763_0 clientBuilder = clientBuilder.WithBroker(); +#elif NETCOREAPP3_1 + clientBuilder = clientBuilder.WithWindowsBroker(); #endif clientBuilder = (redirectUri != null) diff --git a/SampleTest/SampleTest.csproj b/SampleTest/SampleTest.csproj index 58e74aa..309669f 100644 --- a/SampleTest/SampleTest.csproj +++ b/SampleTest/SampleTest.csproj @@ -155,7 +155,6 @@ - diff --git a/Samples/WpfNet5WindowsMsalProviderSample/App.xaml b/Samples/WpfNet5WindowsMsalProviderSample/App.xaml new file mode 100644 index 0000000..35cad72 --- /dev/null +++ b/Samples/WpfNet5WindowsMsalProviderSample/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Samples/WpfMsalProviderSample/App.xaml.cs b/Samples/WpfNet5WindowsMsalProviderSample/App.xaml.cs similarity index 97% rename from Samples/WpfMsalProviderSample/App.xaml.cs rename to Samples/WpfNet5WindowsMsalProviderSample/App.xaml.cs index c964c9e..928b0ec 100644 --- a/Samples/WpfMsalProviderSample/App.xaml.cs +++ b/Samples/WpfNet5WindowsMsalProviderSample/App.xaml.cs @@ -9,7 +9,7 @@ using CommunityToolkit.Authentication.Extensions; using Microsoft.Identity.Client.Extensions.Msal; -namespace WpfMsalProviderSample +namespace WpfNet5WindowsMsalProviderSample { public partial class App : Application { diff --git a/Samples/WpfMsalProviderSample/AssemblyInfo.cs b/Samples/WpfNet5WindowsMsalProviderSample/AssemblyInfo.cs similarity index 100% rename from Samples/WpfMsalProviderSample/AssemblyInfo.cs rename to Samples/WpfNet5WindowsMsalProviderSample/AssemblyInfo.cs diff --git a/Samples/WpfNet5WindowsMsalProviderSample/CacheConfig.cs b/Samples/WpfNet5WindowsMsalProviderSample/CacheConfig.cs new file mode 100644 index 0000000..5986a84 --- /dev/null +++ b/Samples/WpfNet5WindowsMsalProviderSample/CacheConfig.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.Identity.Client.Extensions.Msal; + +namespace WpfNet5WindowsMsalProviderSample +{ + /// + /// https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet/wiki/Cross-platform-Token-Cache + /// + class CacheConfig + { + public const string CacheFileName = "msal_cache.dat"; + public const string CacheDir = "MSAL_CACHE"; + + public const string KeyChainServiceName = "msal_service"; + public const string KeyChainAccountName = "msal_account"; + + public const string LinuxKeyRingSchema = "com.contoso.devtools.tokencache"; + public const string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection; + public const string LinuxKeyRingLabel = "MSAL token cache for all Contoso dev tool apps."; + public static readonly KeyValuePair LinuxKeyRingAttr1 = new KeyValuePair("Version", "1"); + public static readonly KeyValuePair LinuxKeyRingAttr2 = new KeyValuePair("ProductGroup", "MyApps"); + } +} diff --git a/Samples/WpfNet5WindowsMsalProviderSample/LoginButton.xaml b/Samples/WpfNet5WindowsMsalProviderSample/LoginButton.xaml new file mode 100644 index 0000000..d6dfc4f --- /dev/null +++ b/Samples/WpfNet5WindowsMsalProviderSample/LoginButton.xaml @@ -0,0 +1,14 @@ + + +