From b5d7d92ca1aa08854e0911840bc6f96595333db4 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Sun, 29 Aug 2021 07:34:46 -0700 Subject: [PATCH 1/8] Helper to provide higher level API for PCA --- .../UserDetailsClient.Droid/MainActivity.cs | 6 +- .../UserDetailsClient.UWP/App.xaml.cs | 4 +- .../UserDetailsClient/App.cs | 10 +- .../UserDetailsClient/MainPage.xaml.cs | 59 ++---- .../UserDetailsClient/PCAHelper.cs | 184 ++++++++++++++++++ .../UserDetailsClient.Droid/MainActivity.cs | 6 +- .../UserDetailsClient.UWP/App.xaml.cs | 4 +- .../UserDetailsClient/App.cs | 6 - .../UserDetailsClient/MainPage.xaml | 3 +- .../UserDetailsClient/MainPage.xaml.cs | 115 ++++------- .../UserDetailsClient/PCAHelper.cs | 184 ++++++++++++++++++ 11 files changed, 431 insertions(+), 150 deletions(-) create mode 100644 1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs create mode 100644 2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs diff --git a/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs b/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs index d353737..3eb1743 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs @@ -9,7 +9,8 @@ using Microsoft.Identity.Client; using Android.Content; using Microsoft.Identity.Client.Platforms.Android; - +using Microsoft.Identity.Client.Helper; + namespace UserDetailsClient.Droid { [Activity(Label = "UserDetailsClient", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] @@ -21,7 +22,8 @@ protected override void OnCreate(Bundle bundle) global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); - App.ParentWindow = this; + App.ParentWindow = this; + PCAHelper.ParentWindow = this; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs index cdf7f01..928d17c 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Identity.Client.Helper; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -30,6 +31,7 @@ public App() { this.InitializeComponent(); this.Suspending += OnSuspending; + PCAHelper.IsUWP = true; } /// diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs index 9f47761..c75b7e8 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs @@ -1,4 +1,5 @@ using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Helper; using System; using Xamarin.Forms; @@ -6,8 +7,6 @@ namespace UserDetailsClient { public class App : Application { - public static IPublicClientApplication PCA = null; - /// /// The ClientID is the Application ID found in the portal (https://go.microsoft.com/fwlink/?linkid=2083908). /// You can use the below id however if you create an app of your own you should replace the value here. @@ -20,11 +19,8 @@ public class App : Application public static object ParentWindow { get; set; } public App(string specialRedirectUri = null) - { - PCA = PublicClientApplicationBuilder.Create(ClientID) - .WithRedirectUri(specialRedirectUri?? $"msal{ClientID}://auth") - .WithIosKeychainSecurityGroup("com.microsoft.adalcache") - .Build(); + { + PCAHelper.Init(ClientID, Scopes); MainPage = new NavigationPage(new UserDetailsClient.MainPage()); } diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 06e924b..bf2c531 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -1,4 +1,5 @@ using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Helper; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -19,65 +20,29 @@ public MainPage() async void OnSignInSignOut(object sender, EventArgs e) { - AuthenticationResult authResult = null; - IEnumerable accounts = await App.PCA.GetAccountsAsync().ConfigureAwait(false); try { if (btnSignInSignOut.Text == "Sign in") { - try + var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(customizeInteractive: (builder) => { - IAccount firstAccount = accounts.FirstOrDefault(); - authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount) - .ExecuteAsync() - .ConfigureAwait(false); - } - catch (MsalUiRequiredException) - { - try - { - var builder = App.PCA.AcquireTokenInteractive(App.Scopes) - .WithParentActivityOrWindow(App.ParentWindow); - - if (Device.RuntimePlatform != "UWP") - { - // on Android and iOS, prefer to use the system browser, which does not exist on UWP - SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() - { - iOSHidePrivacyPrompt = true, - }; - - builder.WithSystemWebViewOptions(systemWebViewOptions); - builder.WithUseEmbeddedWebView(false); - } - - authResult = await builder.ExecuteAsync().ConfigureAwait(false); - } - catch (Exception ex2) - { - await DisplayAlert("Acquire token interactive failed. See exception message for details: ", ex2.Message, "Dismiss"); - } - } + builder.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount); + }); if (authResult != null) { - var content = await GetHttpContentWithTokenAsync(authResult.AccessToken); + var content = await GetHttpContentWithTokenAsync(); UpdateUserContent(content); } } else { - while (accounts.Any()) - { - await App.PCA.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(false); - accounts = await App.PCA.GetAccountsAsync().ConfigureAwait(false); - } + await PCAHelper.Instance.SignOutAsync(); - - Device.BeginInvokeOnMainThread(() => + Device.BeginInvokeOnMainThread(() => { slUser.IsVisible = false; - btnSignInSignOut.Text = "Sign in"; + btnSignInSignOut.Text = "Sign in"; }); } } @@ -89,7 +54,7 @@ async void OnSignInSignOut(object sender, EventArgs e) private void UpdateUserContent(string content) { - if(!string.IsNullOrEmpty(content)) + if (!string.IsNullOrEmpty(content)) { JObject user = JObject.Parse(content); @@ -108,19 +73,19 @@ private void UpdateUserContent(string content) } } - public async Task GetHttpContentWithTokenAsync(string token) + public async Task GetHttpContentWithTokenAsync() { try { //get data from API HttpClient client = new HttpClient(); HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me"); - message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); + PCAHelper.Instance.AddAuthenticationBearerToken(message); HttpResponseMessage response = await client.SendAsync(message).ConfigureAwait(false); string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return responseString; } - catch(Exception ex) + catch (Exception ex) { await DisplayAlert("API call to graph failed: ", ex.Message, "Dismiss").ConfigureAwait(false); return ex.ToString(); diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs new file mode 100644 index 0000000..02f4b56 --- /dev/null +++ b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Identity.Client.Helper +{ + /// + /// This helper class encapsulates the common functionality used in the Apps + /// At the same time, developers can customizes its behavior in two places + /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA + /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called + /// + public class PCAHelper + { + /// + /// Instance of the helper + /// + public static PCAHelper Instance { get; private set; } + + /// + /// IPublicClientApplication that is created on the first get + /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get + /// + public IPublicClientApplication PCA + { + get + { + if (_pca == null) + { + _pca = PCABuilder.Build(); + } + + return _pca; + } + } + + // Instance of IPublicClientApplication + private IPublicClientApplication _pca; + + /// + /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get + /// + public PublicClientApplicationBuilder PCABuilder { get; private set; } + + /// + /// This is applicable to Android. Please update this property in MainActivity.Create method + /// and consequently with change in the current activity. + /// + public static object ParentWindow { get; set; } = null; + + /// + /// In UWP app, set it to true. + /// + public static bool IsUWP { get; set; } = false; + + /// + /// This stores the authentication result, from the auth process. + /// When the process starts, it is set to null. + /// + public AuthenticationResult AuthResult { get; private set; } + + // desired scopes + private string[] _scopes; + + // Private constructor to keep it singleton + private PCAHelper() + { + } + + /// + /// Initializes the instance and PublicClientApplicationBuilder with the give parameters + /// PublicClientApplicationBuilder can be customized after the call. + /// + /// Client id of your application + /// The desired scope + /// If you are using recommended pattern fo rredirect Uri, this is optional + /// + public static PCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null) + { + if (Instance == null) + { + Instance = new PCAHelper(); + Instance._scopes = scopes; + Instance.PCABuilder = PublicClientApplicationBuilder.Create(clientId) + .WithRedirectUri(specialRedirectUri ?? $"msal{clientId}://auth") + .WithIosKeychainSecurityGroup("com.microsoft.adalcache"); + } + return Instance; + } + + /// + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt interactively, + /// Interactive attempt is optional. + /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. + /// + /// If true, does not attempt UI interaction even if silent action fails + /// Account to be used. (optional) + /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute + /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute + /// + public async Task EnsureAuthenticatedAsync(bool silentOnly = false, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) + { + AuthResult = null; + + try + { + // Customize silentBuilder + var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); + if (customizeSilent != null) + { + customizeSilent(silentparamsBuilder); + } + + AuthResult = await silentparamsBuilder.ExecuteAsync() + .ConfigureAwait(false); + } + catch (MsalUiRequiredException) + { + if (!silentOnly) + { + try + { + var builder = PCA.AcquireTokenInteractive(_scopes) + .WithParentActivityOrWindow(ParentWindow); + + if (!IsUWP) + { + // on Android and iOS, prefer to use the system browser, which does not exist on UWP + SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() + { + iOSHidePrivacyPrompt = true, + }; + + builder.WithSystemWebViewOptions(systemWebViewOptions); + builder.WithUseEmbeddedWebView(false); + } + + if (customizeInteractive != null) + { + customizeInteractive(builder); + } + + AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + throw; + } + } + } + + return AuthResult; + } + + /// + /// This will remove all the accounts. + /// + /// + public async Task SignOutAsync() + { + IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + + while (accounts.Any()) + { + await PCA.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(false); + accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + } + + AuthResult = null; + } + + /// + /// This will add bearer token to request message from the Authentication result + /// + /// + public void AddAuthenticationBearerToken(HttpRequestMessage message) + { + message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AuthResult.AccessToken); + } + } +} diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs index d353737..4fc50b5 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs @@ -9,7 +9,8 @@ using Microsoft.Identity.Client; using Android.Content; using Microsoft.Identity.Client.Platforms.Android; - +using Microsoft.Identity.Client.Helper; + namespace UserDetailsClient.Droid { [Activity(Label = "UserDetailsClient", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] @@ -21,7 +22,8 @@ protected override void OnCreate(Bundle bundle) global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); - App.ParentWindow = this; + App.ParentWindow = this; + PCAHelper.ParentWindow = this; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs index cdf7f01..928d17c 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Identity.Client.Helper; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -30,6 +31,7 @@ public App() { this.InitializeComponent(); this.Suspending += OnSuspending; + PCAHelper.IsUWP = true; } /// diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs index 5539da2..a22ef3c 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs @@ -5,8 +5,6 @@ namespace UserDetailsClient { public class App : Application { - public static IPublicClientApplication PCA = null; - /// /// The ClientID is the Application ID found in the portal (https://go.microsoft.com/fwlink/?linkid=2083908). /// You can use the below id however if you create an app of your own you should replace the value here. @@ -29,10 +27,6 @@ public class App : Application public App() { - PCA = PublicClientApplicationBuilder.Create(ClientID) - .WithRedirectUri($"msal{ClientID}://auth") - .Build(); - MainPage = new NavigationPage(new UserDetailsClient.MainPage()); } diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml index 4039ee1..7f3c9cd 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml @@ -30,8 +30,7 @@ - - /// If true, does not attempt UI interaction even if silent action fails + /// Attempts silent acquire based on the value + /// UI interaction even if silent action fails< /// Account to be used. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute /// - public async Task EnsureAuthenticatedAsync(bool silentOnly = false, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) + public async Task EnsureAuthenticatedAsync(bool doSilent = true, bool doInteractive = true, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) { AuthResult = null; try { - // Customize silentBuilder - var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); - if (customizeSilent != null) + if (doSilent) { - customizeSilent(silentparamsBuilder); - } + // Customize silentBuilder + var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); + if (customizeSilent != null) + { + customizeSilent(silentparamsBuilder); + } - AuthResult = await silentparamsBuilder.ExecuteAsync() - .ConfigureAwait(false); + AuthResult = await silentparamsBuilder.ExecuteAsync() + .ConfigureAwait(false); + } + else if (doInteractive) + { + await AcquireInteractive(customizeInteractive).ConfigureAwait(false); + } + else + { + throw new ArgumentException($"Both doSilent and do Interactive cannot be false"); + } } catch (MsalUiRequiredException) { - if (!silentOnly) + if (doInteractive) { - try - { - var builder = PCA.AcquireTokenInteractive(_scopes) - .WithParentActivityOrWindow(ParentWindow); - - if (!IsUWP) - { - // on Android and iOS, prefer to use the system browser, which does not exist on UWP - SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() - { - iOSHidePrivacyPrompt = true, - }; - - builder.WithSystemWebViewOptions(systemWebViewOptions); - builder.WithUseEmbeddedWebView(false); - } - - if (customizeInteractive != null) - { - customizeInteractive(builder); - } - - AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.Message); - throw; - } + await AcquireInteractive(customizeInteractive).ConfigureAwait(false); } } return AuthResult; } + // acquire interactively. + private async Task AcquireInteractive(Action customizeInteractive) + { + try + { + var builder = PCA.AcquireTokenInteractive(_scopes) + .WithParentActivityOrWindow(ParentWindow); + + if (!IsUWP) + { + // on Android and iOS, prefer to use the system browser, which does not exist on UWP + SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() + { + iOSHidePrivacyPrompt = true, + }; + + builder.WithSystemWebViewOptions(systemWebViewOptions); + builder.WithUseEmbeddedWebView(false); + } + + if (customizeInteractive != null) + { + customizeInteractive(builder); + } + + AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + throw; + } + } + /// /// This will remove all the accounts. /// diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs index 4fc50b5..b05a4ca 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs @@ -21,8 +21,7 @@ protected override void OnCreate(Bundle bundle) base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); - LoadApplication(new App()); - App.ParentWindow = this; + LoadApplication(new App()); PCAHelper.ParentWindow = this; } diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs index f6d6ecc..d86bbed 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs @@ -6,6 +6,7 @@ using UIKit; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Platforms.iOS; +using Microsoft.Identity.Client.Helper; namespace UserDetailsClient.iOS { @@ -26,7 +27,7 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options) { global::Xamarin.Forms.Forms.Init(); LoadApplication(new App()); - App.ParentWindow = new UIViewController(); // iOS broker requires a view controller + PCAHelper.ParentWindow = new UIViewController(); // iOS broker requires a view controller return base.FinishedLaunching(app, options); } diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs index a22ef3c..708eded 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/App.cs @@ -23,8 +23,6 @@ public class App : Application public static string[] Scopes = { "User.Read" }; public static string Username = string.Empty; - public static object ParentWindow { get; set; } - public App() { MainPage = new NavigationPage(new UserDetailsClient.MainPage()); diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 9fbc03c..0076ae9 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -46,8 +46,6 @@ private void UpdateUserContent(string content) { JObject user = JObject.Parse(content); - slUser.IsVisible = true; - Device.BeginInvokeOnMainThread(() => { lblDisplayName.Text = user["displayName"].ToString(); @@ -56,21 +54,23 @@ private void UpdateUserContent(string content) lblSurname.Text = user["surname"].ToString(); lblUserPrincipalName.Text = user["userPrincipalName"].ToString(); + slUser.IsVisible = true; + btnSignInSignOut.Text = "Sign out"; }); } } - public async Task GetHttpContentWithTokenAsync(string token) + public async Task GetHttpContentWithTokenAsync() { try { //get data from API HttpClient client = new HttpClient(); HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me"); - message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - HttpResponseMessage response = await client.SendAsync(message); - string responseString = await response.Content.ReadAsStringAsync(); + PCAHelper.Instance.AddAuthenticationBearerToken(message); + HttpResponseMessage response = await client.SendAsync(message).ConfigureAwait(false); + string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return responseString; } catch (Exception ex) @@ -82,35 +82,38 @@ public async Task GetHttpContentWithTokenAsync(string token) private async void btnSignInSignOutWithBroker_Clicked(object sender, EventArgs e) { - AuthenticationResult authResult = null; - IEnumerable accounts = await PCAHelper.Instance.PCA.GetAccountsAsync(); try { if (PCAHelper.Instance.AuthResult == null) - { - authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(account: accounts.FirstOrDefault()); + { + IEnumerable accounts = await PCAHelper.Instance.PCA.GetAccountsAsync().ConfigureAwait(false); ; + await PCAHelper.Instance.EnsureAuthenticatedAsync(account: accounts.FirstOrDefault()).ConfigureAwait(false); - if (authResult != null) + if (PCAHelper.Instance.AuthResult != null) { - var content = await GetHttpContentWithTokenAsync(authResult.AccessToken); - UpdateUserContent(content); + var content = await GetHttpContentWithTokenAsync().ConfigureAwait(false); Device.BeginInvokeOnMainThread(() => - { + { + UpdateUserContent(content); btnSignInSignOut.Text = "Sign out"; }); } } else { - await PCAHelper.Instance.SignOutAsync(); + await PCAHelper.Instance.SignOutAsync().ConfigureAwait(false); - slUser.IsVisible = false; - Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign in with Broker"; }); + + Device.BeginInvokeOnMainThread(() => + { + slUser.IsVisible = false; + btnSignInSignOut.Text = "Sign in with Broker"; + }); } } catch (Exception ex) { - await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss"); + await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss").ConfigureAwait(false); } } } diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs index e38db82..a79cb76 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs @@ -95,66 +95,84 @@ public static PCAHelper Init(string clientId, string[] scopes, string specialRed /// Interactive attempt is optional. /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. /// - /// If true, does not attempt UI interaction even if silent action fails + /// Attempts silent acquire based on the value + /// UI interaction even if silent action fails< /// Account to be used. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute /// - public async Task EnsureAuthenticatedAsync(bool silentOnly = false, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) + public async Task EnsureAuthenticatedAsync(bool doSilent = true, bool doInteractive = true, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) { AuthResult = null; try { - // Customize silentBuilder - var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); - if (customizeSilent != null) + if (doSilent) { - customizeSilent(silentparamsBuilder); + // Customize silentBuilder + var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); + if (customizeSilent != null) + { + customizeSilent(silentparamsBuilder); + } + + AuthResult = await silentparamsBuilder.ExecuteAsync() + .ConfigureAwait(false); + } + else if (doInteractive) + { + await AcquireInteractive(customizeInteractive).ConfigureAwait(false); + } + else + { + throw new ArgumentException($"Both doSilent and do Interactive cannot be false"); } - - AuthResult = await silentparamsBuilder.ExecuteAsync() - .ConfigureAwait(false); } catch (MsalUiRequiredException) { - if (!silentOnly) + if (doInteractive) { - try - { - var builder = PCA.AcquireTokenInteractive(_scopes) - .WithParentActivityOrWindow(ParentWindow); - - if (!IsUWP) - { - // on Android and iOS, prefer to use the system browser, which does not exist on UWP - SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() - { - iOSHidePrivacyPrompt = true, - }; - - builder.WithSystemWebViewOptions(systemWebViewOptions); - builder.WithUseEmbeddedWebView(false); - } - - if (customizeInteractive != null) - { - customizeInteractive(builder); - } - - AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.Message); - throw; - } + await AcquireInteractive(customizeInteractive).ConfigureAwait(false); } } return AuthResult; } + // acquire interactively. + private async Task AcquireInteractive(Action customizeInteractive) + { + try + { + var builder = PCA.AcquireTokenInteractive(_scopes) + .WithParentActivityOrWindow(ParentWindow); + + if (!IsUWP) + { + // on Android and iOS, prefer to use the system browser, which does not exist on UWP + SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() + { + iOSHidePrivacyPrompt = true, + }; + + builder.WithSystemWebViewOptions(systemWebViewOptions); + builder.WithUseEmbeddedWebView(false); + } + + if (customizeInteractive != null) + { + customizeInteractive(builder); + } + + AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + throw; + } + } + /// /// This will remove all the accounts. /// From 5db8e67597456a25694d19c27537950e4dc6f817 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Sun, 17 Oct 2021 16:42:41 -0700 Subject: [PATCH 3/8] Ability to custom select account --- .../UserDetailsClient/MainPage.xaml.cs | 6 +++--- .../UserDetailsClient/PCAHelper.cs | 20 +++++++++++++++++-- .../UserDetailsClient/MainPage.xaml | 4 ++-- .../UserDetailsClient/MainPage.xaml.cs | 6 ++---- .../UserDetailsClient/PCAHelper.cs | 20 +++++++++++++++++-- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index bf2c531..50eb100 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -27,17 +27,17 @@ async void OnSignInSignOut(object sender, EventArgs e) var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(customizeInteractive: (builder) => { builder.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount); - }); + }).ConfigureAwait(false); if (authResult != null) { - var content = await GetHttpContentWithTokenAsync(); + var content = await GetHttpContentWithTokenAsync().ConfigureAwait(false); UpdateUserContent(content); } } else { - await PCAHelper.Instance.SignOutAsync(); + await PCAHelper.Instance.SignOutAsync().ConfigureAwait(false); Device.BeginInvokeOnMainThread(() => { diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs index a79cb76..e7bbe34 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs @@ -97,11 +97,16 @@ public static PCAHelper Init(string clientId, string[] scopes, string specialRed /// /// Attempts silent acquire based on the value /// UI interaction even if silent action fails< - /// Account to be used. (optional) + /// Function that determines th account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute /// - public async Task EnsureAuthenticatedAsync(bool doSilent = true, bool doInteractive = true, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) + public async Task EnsureAuthenticatedAsync( + bool doSilent = true, + bool doInteractive = true, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null) { AuthResult = null; @@ -109,6 +114,17 @@ public async Task EnsureAuthenticatedAsync(bool doSilent = { if (doSilent) { + IAccount account = null; + IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + if (preferredAccount != null) + { + account = preferredAccount(accounts); + } + else if(accounts != null) + { + account = accounts.FirstOrDefault(); + } + // Customize silentBuilder var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); if (customizeSilent != null) diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml index 7f3c9cd..c89c0f2 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml @@ -4,10 +4,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="UserDetailsClient.MainPage"> - + /// Attempts silent acquire based on the value /// UI interaction even if silent action fails< - /// Account to be used. (optional) + /// Function that determines th account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute /// - public async Task EnsureAuthenticatedAsync(bool doSilent = true, bool doInteractive = true, IAccount account = null, Action customizeSilent = null, Action customizeInteractive = null) + public async Task EnsureAuthenticatedAsync( + bool doSilent = true, + bool doInteractive = true, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null) { AuthResult = null; @@ -109,6 +114,17 @@ public async Task EnsureAuthenticatedAsync(bool doSilent = { if (doSilent) { + IAccount account = null; + IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + if (preferredAccount != null) + { + account = preferredAccount(accounts); + } + else if(accounts != null) + { + account = accounts.FirstOrDefault(); + } + // Customize silentBuilder var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); if (customizeSilent != null) From c1e7904fc45939c802ec1999f2f27ca96c6cb9e6 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Sun, 14 Nov 2021 17:54:18 -0800 Subject: [PATCH 4/8] Created IPCAHelper and Init is customizable Basic --- .../UserDetailsClient.Droid/MainActivity.cs | 2 +- .../UserDetailsClient.UWP/App.xaml.cs | 1 - .../UserDetailsClient.UWP/MainPage.xaml.cs | 6 +- .../UserDetailsClient/App.cs | 4 +- .../UserDetailsClient/IPCAHelper.cs | 75 +++++++++++++++++++ .../UserDetailsClient/MainPage.xaml.cs | 2 +- .../UserDetailsClient/PCAHelper.cs | 33 ++++---- 7 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs diff --git a/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs b/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs index 3eb1743..ef1f9ac 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs @@ -23,7 +23,7 @@ protected override void OnCreate(Bundle bundle) global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); App.ParentWindow = this; - PCAHelper.ParentWindow = this; + PCAHelper.Instance.ParentWindow = this; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs index 928d17c..2ff0d74 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/App.xaml.cs @@ -31,7 +31,6 @@ public App() { this.InitializeComponent(); this.Suspending += OnSuspending; - PCAHelper.IsUWP = true; } /// diff --git a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/MainPage.xaml.cs index abe8299..e8e832a 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient.UWP/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient.UWP/MainPage.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Identity.Client.Helper; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -32,7 +33,8 @@ public MainPage() string redirectUriWithWAM = $"ms-appx-web://microsoft.aad.brokerplugin/{sid}"; // Then use the following: - LoadApplication(new UserDetailsClient.App(redirectURIForSsoWithoutBroker.AbsoluteUri)); + LoadApplication(new UserDetailsClient.App(redirectURIForSsoWithoutBroker.AbsoluteUri)); + PCAHelper.Instance.IsUWP = true; } } } diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs index c75b7e8..068969c 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs @@ -18,9 +18,11 @@ public class App : Application public static object ParentWindow { get; set; } + public static IPCAHelper PCA { get; private set; } + public App(string specialRedirectUri = null) { - PCAHelper.Init(ClientID, Scopes); + PCA = PCAHelper.Init(ClientID, Scopes); MainPage = new NavigationPage(new UserDetailsClient.MainPage()); } diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs b/1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs new file mode 100644 index 0000000..a30c34a --- /dev/null +++ b/1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Identity.Client.Helper +{ + /// + /// This helper class encapsulates the common functionality used in the Apps + /// At the same time, developers can customizes its behavior in two places + /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA + /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called + /// + public interface IPCAHelper + { + /// + /// IPublicClientApplication that is created on the first get + /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get + /// + IPublicClientApplication PCA { get; } + + /// + /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get + /// + PublicClientApplicationBuilder PCABuilder { get; } + + /// + /// This is applicable to Android. Please update this property in MainActivity.Create method + /// and consequently with change in the current activity. + /// + object ParentWindow { get; set; } + + /// + /// In UWP app, set it to true. + /// + bool IsUWP { get; set; } + + /// + /// This stores the authentication result, from the auth process. + /// When the process starts, it is set to null. + /// + AuthenticationResult AuthResult { get; } + + /// + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt interactively, + /// Interactive attempt is optional. + /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. + /// + /// Attempts silent acquire based on the value + /// UI interaction even if silent action fails< + /// Function that determines th account to be used. The default is first. (optional) + /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute + /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute + /// + Task EnsureAuthenticatedAsync( + bool doSilent = true, + bool doInteractive = true, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null); + + /// + /// This will remove all the accounts. + /// + /// + Task SignOutAsync(); + + /// + /// This will add bearer token to request message from the Authentication result + /// + /// + void AddAuthenticationBearerToken(HttpRequestMessage message); + } +} diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 50eb100..4afbea6 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -48,7 +48,7 @@ async void OnSignInSignOut(object sender, EventArgs e) } catch (Exception ex) { - await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss"); + await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss").ConfigureAwait(false); } } diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs index e7bbe34..719a8c0 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs @@ -12,12 +12,12 @@ namespace Microsoft.Identity.Client.Helper /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called /// - public class PCAHelper + public class PCAHelper : IPCAHelper { /// /// Instance of the helper /// - public static PCAHelper Instance { get; private set; } + public static IPCAHelper Instance { get; private set; } /// /// IPublicClientApplication that is created on the first get @@ -48,44 +48,43 @@ public IPublicClientApplication PCA /// This is applicable to Android. Please update this property in MainActivity.Create method /// and consequently with change in the current activity. /// - public static object ParentWindow { get; set; } = null; + public object ParentWindow { get; set; } = null; /// /// In UWP app, set it to true. /// - public static bool IsUWP { get; set; } = false; + public bool IsUWP { get; set; } = false; /// /// This stores the authentication result, from the auth process. /// When the process starts, it is set to null. /// - public AuthenticationResult AuthResult { get; private set; } + public AuthenticationResult AuthResult { get; internal set; } // desired scopes private string[] _scopes; - // Private constructor to keep it singleton - private PCAHelper() - { - } - /// - /// Initializes the instance and PublicClientApplicationBuilder with the give parameters + /// Initializes the instance and PublicClientApplicationBuilder with the given parameters /// PublicClientApplicationBuilder can be customized after the call. /// + /// Any class that can is inherited from PCAHelper /// Client id of your application /// The desired scope /// If you are using recommended pattern fo rredirect Uri, this is optional - /// - public static PCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null) + /// Creates a new instance irrespective of the previous instance + /// Instance of class inherited from PCAHelper + public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) + where T : PCAHelper, new() { - if (Instance == null) + if (Instance == null || forceCreate) { - Instance = new PCAHelper(); - Instance._scopes = scopes; - Instance.PCABuilder = PublicClientApplicationBuilder.Create(clientId) + var pcaHelper = new T(); + pcaHelper._scopes = scopes; + pcaHelper.PCABuilder = PublicClientApplicationBuilder.Create(clientId) .WithRedirectUri(specialRedirectUri ?? $"msal{clientId}://auth") .WithIosKeychainSecurityGroup("com.microsoft.adalcache"); + Instance = pcaHelper; } return Instance; } From cc09859c11e93944519715221d5492ce2c226261 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Thu, 23 Dec 2021 15:50:31 -0800 Subject: [PATCH 5/8] IPCAHelper for broker, common folder and doc. --- .../UserDetailsClient.csproj | 8 + .../UserDetailsClient.Droid/MainActivity.cs | 2 +- .../UserDetailsClient.iOS/AppDelegate.cs | 2 +- .../UserDetailsClient/MainPage.xaml.cs | 2 +- .../UserDetailsClient/PCAHelper.cs | 218 ------------------ .../UserDetailsClient.csproj | 8 + .../IPCAHelper.cs | 28 +-- Helper/PCA Helper.md | 105 +++++++++ .../UserDetailsClient => Helper}/PCAHelper.cs | 39 ++-- 9 files changed, 160 insertions(+), 252 deletions(-) delete mode 100644 2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs rename {1-Basic/UserDetailsClient/UserDetailsClient => Helper}/IPCAHelper.cs (70%) create mode 100644 Helper/PCA Helper.md rename {1-Basic/UserDetailsClient/UserDetailsClient => Helper}/PCAHelper.cs (81%) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj b/1-Basic/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj index 395c434..513096e 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj +++ b/1-Basic/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj @@ -15,4 +15,12 @@ + + + Helper\PCAHelper.cs + + + Helper\IPCAHelper.cs + + \ No newline at end of file diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs index b05a4ca..8e25751 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/MainActivity.cs @@ -22,7 +22,7 @@ protected override void OnCreate(Bundle bundle) global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); - PCAHelper.ParentWindow = this; + PCAHelper.Instance.ParentWindow = this; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs b/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs index d86bbed..60fda2f 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.iOS/AppDelegate.cs @@ -27,7 +27,7 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary options) { global::Xamarin.Forms.Forms.Init(); LoadApplication(new App()); - PCAHelper.ParentWindow = new UIViewController(); // iOS broker requires a view controller + PCAHelper.Instance.ParentWindow = new UIViewController(); // iOS broker requires a view controller return base.FinishedLaunching(app, options); } diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 30a9dae..85f66b1 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -31,7 +31,7 @@ public static void CreatePublicClient() break; } - PCAHelper.Init(App.ClientID, App.Scopes, redirectUri); + PCAHelper.Init(App.ClientID, App.Scopes, redirectUri); if (Device.RuntimePlatform == Device.UWP) { PCAHelper.Instance.PCABuilder.WithExperimentalFeatures(); diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs deleted file mode 100644 index e7bbe34..0000000 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/PCAHelper.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Microsoft.Identity.Client.Helper -{ - /// - /// This helper class encapsulates the common functionality used in the Apps - /// At the same time, developers can customizes its behavior in two places - /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA - /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called - /// - public class PCAHelper - { - /// - /// Instance of the helper - /// - public static PCAHelper Instance { get; private set; } - - /// - /// IPublicClientApplication that is created on the first get - /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get - /// - public IPublicClientApplication PCA - { - get - { - if (_pca == null) - { - _pca = PCABuilder.Build(); - } - - return _pca; - } - } - - // Instance of IPublicClientApplication - private IPublicClientApplication _pca; - - /// - /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get - /// - public PublicClientApplicationBuilder PCABuilder { get; private set; } - - /// - /// This is applicable to Android. Please update this property in MainActivity.Create method - /// and consequently with change in the current activity. - /// - public static object ParentWindow { get; set; } = null; - - /// - /// In UWP app, set it to true. - /// - public static bool IsUWP { get; set; } = false; - - /// - /// This stores the authentication result, from the auth process. - /// When the process starts, it is set to null. - /// - public AuthenticationResult AuthResult { get; private set; } - - // desired scopes - private string[] _scopes; - - // Private constructor to keep it singleton - private PCAHelper() - { - } - - /// - /// Initializes the instance and PublicClientApplicationBuilder with the give parameters - /// PublicClientApplicationBuilder can be customized after the call. - /// - /// Client id of your application - /// The desired scope - /// If you are using recommended pattern fo rredirect Uri, this is optional - /// - public static PCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null) - { - if (Instance == null) - { - Instance = new PCAHelper(); - Instance._scopes = scopes; - Instance.PCABuilder = PublicClientApplicationBuilder.Create(clientId) - .WithRedirectUri(specialRedirectUri ?? $"msal{clientId}://auth") - .WithIosKeychainSecurityGroup("com.microsoft.adalcache"); - } - return Instance; - } - - /// - /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt interactively, - /// Interactive attempt is optional. - /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. - /// - /// Attempts silent acquire based on the value - /// UI interaction even if silent action fails< - /// Function that determines th account to be used. The default is first. (optional) - /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute - /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute - /// - public async Task EnsureAuthenticatedAsync( - bool doSilent = true, - bool doInteractive = true, - Func, IAccount> preferredAccount = null, - Action customizeSilent = null, - Action customizeInteractive = null) - { - AuthResult = null; - - try - { - if (doSilent) - { - IAccount account = null; - IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); - if (preferredAccount != null) - { - account = preferredAccount(accounts); - } - else if(accounts != null) - { - account = accounts.FirstOrDefault(); - } - - // Customize silentBuilder - var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); - if (customizeSilent != null) - { - customizeSilent(silentparamsBuilder); - } - - AuthResult = await silentparamsBuilder.ExecuteAsync() - .ConfigureAwait(false); - } - else if (doInteractive) - { - await AcquireInteractive(customizeInteractive).ConfigureAwait(false); - } - else - { - throw new ArgumentException($"Both doSilent and do Interactive cannot be false"); - } - } - catch (MsalUiRequiredException) - { - if (doInteractive) - { - await AcquireInteractive(customizeInteractive).ConfigureAwait(false); - } - } - - return AuthResult; - } - - // acquire interactively. - private async Task AcquireInteractive(Action customizeInteractive) - { - try - { - var builder = PCA.AcquireTokenInteractive(_scopes) - .WithParentActivityOrWindow(ParentWindow); - - if (!IsUWP) - { - // on Android and iOS, prefer to use the system browser, which does not exist on UWP - SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions() - { - iOSHidePrivacyPrompt = true, - }; - - builder.WithSystemWebViewOptions(systemWebViewOptions); - builder.WithUseEmbeddedWebView(false); - } - - if (customizeInteractive != null) - { - customizeInteractive(builder); - } - - AuthResult = await builder.ExecuteAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex.Message); - throw; - } - } - - /// - /// This will remove all the accounts. - /// - /// - public async Task SignOutAsync() - { - IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); - - while (accounts.Any()) - { - await PCA.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(false); - accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); - } - - AuthResult = null; - } - - /// - /// This will add bearer token to request message from the Authentication result - /// - /// - public void AddAuthenticationBearerToken(HttpRequestMessage message) - { - message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AuthResult.AccessToken); - } - } -} diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj b/2-With-broker/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj index 6e631a1..8bdcd6a 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/UserDetailsClient.csproj @@ -18,4 +18,12 @@ + + + Helper\PCAHelper.cs + + + Helper\IPCAHelper.cs + + \ No newline at end of file diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs b/Helper/IPCAHelper.cs similarity index 70% rename from 1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs rename to Helper/IPCAHelper.cs index a30c34a..d747615 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/IPCAHelper.cs +++ b/Helper/IPCAHelper.cs @@ -7,10 +7,11 @@ namespace Microsoft.Identity.Client.Helper { /// - /// This helper class encapsulates the common functionality used in the Apps - /// At the same time, developers can customizes its behavior in two places + /// This is the interface for helper class to encapsulate the calling patterns used in the Public Client Applications. + /// At the same time, developers can customizes its behaviors /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA - /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called + /// 2. EnsureAuthenticatedAsync has delegates to customize AcquireTokenInteractiveParameterBuilder, AcquireTokenSilentParameterBuilder and selection of account + /// before excute is called. /// public interface IPCAHelper { @@ -43,16 +44,16 @@ public interface IPCAHelper AuthenticationResult AuthResult { get; } /// - /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt interactively, + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt acquire token interactively. /// Interactive attempt is optional. - /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. + /// It provides optional delegates to customize behavior. /// - /// Attempts silent acquire based on the value - /// UI interaction even if silent action fails< - /// Function that determines th account to be used. The default is first. (optional) - /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute - /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute - /// + /// Determines whether to execute AcquireTokenSilent + /// Determines whether to execute AcquireTokenInteractive. By detault, UI interaction takes place if silent action fails. + /// Function that determines the account to be used. The default is first. (optional) + /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. + /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. + /// Authenitcation result Task EnsureAuthenticatedAsync( bool doSilent = true, bool doInteractive = true, @@ -67,9 +68,10 @@ Task EnsureAuthenticatedAsync( Task SignOutAsync(); /// - /// This will add bearer token to request message from the Authentication result + /// This will add bearer token to request message as per the Authentication result. + /// It is assumed that the class has valid AuthenticationResult /// - /// + /// Message that needs token void AddAuthenticationBearerToken(HttpRequestMessage message); } } diff --git a/Helper/PCA Helper.md b/Helper/PCA Helper.md new file mode 100644 index 0000000..d93a86b --- /dev/null +++ b/Helper/PCA Helper.md @@ -0,0 +1,105 @@ +# PCA Helper +PCA developers use common pattern to acquire token. The code appears to be repeatative and very granular. At the same time, the current API is based on the builder pattern. There are several "With" APIs and a some are used in commonly in the Public Client Application (PCA). Due to the abudance of the With APIs, the learning curve can be high to perform simple/common tasks. + +PCAHelper extracts API at a higher level offering flexibility for granular programming as desired. The helper provides methods with optional paramaeters and lambdas for customization. It has the following features: + +- It comes with two main common practices as default: + - There is only one instance of PCA + - Scope for permissions is defined only once. + The above default can be customized as desired. + - It is inteface based. So the calling app can create Mocks and perform testing w/o havinng dependency on MSAL.NET and any network connectivity + - It allows customization of token acqusition methods. + - It allows specialization of the Helper class should developer choose it. + +# APIs +The APIs are briefly described here. + +## Initialization +This can be done once in the App. This initializes the helper with client id and scope, if it does not have a standrd redirect URI, it can be customized here. By default it PCAHelper is singleton. It can be overwritten by forcing creation. + +```CSharp +public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) + where T : PCAHelper, new() +``` + +Example usage: +```CSharp +// initialize with the client id and scopes. One can optionally pass special redirect URI +// else it creates one with commonly used: $"msal{clientId}://auth" +PCAHelper.Init(B2CConstants.ClientID, B2CConstants.Scopes); +// additional customization of the builder +PCAHelper.Instance.PCABuilder.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp); +``` + +## Obtain the token +This API is for obtaining the token. It attempts to acquire silently and if that fails, raises UI. It provides options to do silent, interative and ability to customizes each parameter builder. +One can also choose the preferred account. + +``` CSharp +public async Task EnsureAuthenticatedAsync( + bool doSilent = true, + bool doInteractive = true, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null) +``` + +Example usage (B2C sample): +``` CSharp +var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync( + doSilent:false, + preferredAccount: (accounts) => GetAccountByPolicy(accounts, B2CConstants.PolicyEditProfile), + customizeInteractive: (builder) => + { + builder.WithPrompt(Prompt.NoPrompt) + .WithAuthority(B2CConstantsAuthorityEditProfile); + }).ConfigureAwait(false); +``` + +## Use the token +User can sign a request without having to deal with the token. + +``` CSharp +public void AddAuthenticationBearerToken(HttpRequestMessage message) +``` + +## Sign out +Here is the API to sign out + +``` CSharp +public async Task SignOutAsync() +``` + +# Properties +Apart from the above, the API provides Instance of the PCHelper that has properties to do more granular programming: + +``` CSharp + + /// + /// IPublicClientApplication that is created on the first get + /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get + /// + IPublicClientApplication PCA { get; } + + /// + /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get + /// + PublicClientApplicationBuilder PCABuilder { get; } + + /// + /// This is applicable to Android. Please update this property in MainActivity.Create method + /// and consequently with change in the current activity. + /// + object ParentWindow { get; set; } + + /// + /// In UWP app, set it to true. + /// + bool IsUWP { get; set; } + + /// + /// This stores the authentication result, from the auth process. + /// When the process starts, it is set to null. + /// + AuthenticationResult AuthResult { get; } +``` \ No newline at end of file diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs b/Helper/PCAHelper.cs similarity index 81% rename from 1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs rename to Helper/PCAHelper.cs index 719a8c0..1c82192 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/PCAHelper.cs +++ b/Helper/PCAHelper.cs @@ -7,10 +7,11 @@ namespace Microsoft.Identity.Client.Helper { /// - /// This helper class encapsulates the common functionality used in the Apps - /// At the same time, developers can customizes its behavior in two places + /// This helper class encapsulates the calling patterns used in the Public Client Applications. + /// At the same time, developers can customizes its behaviors /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA - /// 2. EnsureAuhenticated This has two optional delegates one tocustomize the AcquireTokenInteractiveParameterBuilder and other to customize AcquireTokenSilentParameterBuilder before excute is called + /// 2. EnsureAuthenticatedAsync has delegates to customize AcquireTokenInteractiveParameterBuilder, AcquireTokenSilentParameterBuilder and selection of account + /// before excute is called. /// public class PCAHelper : IPCAHelper { @@ -40,7 +41,7 @@ public IPublicClientApplication PCA private IPublicClientApplication _pca; /// - /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get + /// Application builder. It is created in Init and this member can be customized before Build occurs in PCA->get /// public PublicClientApplicationBuilder PCABuilder { get; private set; } @@ -65,14 +66,15 @@ public IPublicClientApplication PCA private string[] _scopes; /// - /// Initializes the instance and PublicClientApplicationBuilder with the given parameters - /// PublicClientApplicationBuilder can be customized after the call. + /// Initializes the PCAHelpr or its derived class as per the generics and PublicClientApplicationBuilder with the given parameters + /// PublicClientApplicationBuilder can be customized after this method prior to accessing PublicClientApplication. + /// By default it is singleton pattern with option to force creation. /// - /// Any class that can is inherited from PCAHelper + /// Any class that is inherited from PCAHelper /// Client id of your application /// The desired scope /// If you are using recommended pattern fo rredirect Uri, this is optional - /// Creates a new instance irrespective of the previous instance + /// Creates a new instance irrespective of the existance of the previous instance /// Instance of class inherited from PCAHelper public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) where T : PCAHelper, new() @@ -90,16 +92,16 @@ public static IPCAHelper Init(string clientId, string[] scopes, string specia } /// - /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt interactively, + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt acquire token interactively. /// Interactive attempt is optional. - /// If AcquireTokenInteractiveParameterBuilder needs to be cusomized prior to the execution, it provides a delegate. + /// It provides optional delegates to customize behavior. /// - /// Attempts silent acquire based on the value - /// UI interaction even if silent action fails< - /// Function that determines th account to be used. The default is first. (optional) - /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder prior to execute - /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder prior to execute - /// + /// Determines whether to execute AcquireTokenSilent + /// Determines whether to execute AcquireTokenInteractive. By detault, UI interaction takes place if silent action fails. + /// Function that determines the account to be used. The default is first. (optional) + /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. + /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. + /// Authenitcation result public async Task EnsureAuthenticatedAsync( bool doSilent = true, bool doInteractive = true, @@ -206,9 +208,10 @@ public async Task SignOutAsync() } /// - /// This will add bearer token to request message from the Authentication result + /// This will add bearer token to request message as per the Authentication result. + /// It is assumed that the class has valid AuthenticationResult /// - /// + /// Message that needs token public void AddAuthenticationBearerToken(HttpRequestMessage message) { message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AuthResult.AccessToken); From ee545c128822f014e8f8909452db6ecbb71f7fcf Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Fri, 24 Dec 2021 17:40:54 -0800 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: jennyf19 --- Helper/IPCAHelper.cs | 6 ++++-- Helper/PCA Helper.md | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Helper/IPCAHelper.cs b/Helper/IPCAHelper.cs index d747615..d475472 100644 --- a/Helper/IPCAHelper.cs +++ b/Helper/IPCAHelper.cs @@ -8,8 +8,10 @@ namespace Microsoft.Identity.Client.Helper { /// /// This is the interface for helper class to encapsulate the calling patterns used in the Public Client Applications. - /// At the same time, developers can customizes its behaviors - /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA + /// At the same time, developers can customize its behaviors. + + /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customize before accessing PCA. + /// 2. EnsureAuthenticatedAsync has delegates to customize AcquireTokenInteractiveParameterBuilder, AcquireTokenSilentParameterBuilder and selection of account /// before excute is called. /// diff --git a/Helper/PCA Helper.md b/Helper/PCA Helper.md index d93a86b..02bd66c 100644 --- a/Helper/PCA Helper.md +++ b/Helper/PCA Helper.md @@ -15,7 +15,7 @@ PCAHelper extracts API at a higher level offering flexibility for granular progr The APIs are briefly described here. ## Initialization -This can be done once in the App. This initializes the helper with client id and scope, if it does not have a standrd redirect URI, it can be customized here. By default it PCAHelper is singleton. It can be overwritten by forcing creation. +This can be done once in the App. This initializes the helper with client id and scope, if it does not have a standard redirect URI, it can be customized here. By default the PCAHelper is a singleton. It can be overwritten by forcing creation. ```CSharp public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) @@ -32,7 +32,7 @@ PCAHelper.Instance.PCABuilder.WithB2CAuthority(B2CConstants.AuthoritySignInSignU ``` ## Obtain the token -This API is for obtaining the token. It attempts to acquire silently and if that fails, raises UI. It provides options to do silent, interative and ability to customizes each parameter builder. +This API is for obtaining the token. It attempts to acquire a token silently and if that fails, presents an interactive sign-in dialogue to the user. It provides options to do silent, interactive and has the ability to customize each parameter builder. One can also choose the preferred account. ``` CSharp @@ -82,7 +82,7 @@ Apart from the above, the API provides Instance of the PCHelper that has propert IPublicClientApplication PCA { get; } /// - /// Application builder. It is created in Init and thie member can be used to customize it before Build occurs in PCA->get + /// Application builder. It is created in Init and this member can be used to customize it before Build occurs in PCA->get /// PublicClientApplicationBuilder PCABuilder { get; } From c2f5f5f1925950a6cfb815654537296db2c84647 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Sun, 9 Jan 2022 19:34:50 -0800 Subject: [PATCH 7/8] Added tenantID and other parameters --- .../UserDetailsClient/App.cs | 2 +- .../UserDetailsClient/MainPage.xaml.cs | 2 +- .../UserDetailsClient.Droid.csproj | 1 + .../UserDetailsClient/MainPage.xaml.cs | 10 +- Helper/IPCAHelper.cs | 18 +-- Helper/PCAHelper.cs | 107 +++++++++++------- 6 files changed, 81 insertions(+), 59 deletions(-) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs index 068969c..3a0758f 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/App.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/App.cs @@ -22,7 +22,7 @@ public class App : Application public App(string specialRedirectUri = null) { - PCA = PCAHelper.Init(ClientID, Scopes); + PCA = PCAHelper.Init(ClientID, useBroker: false); MainPage = new NavigationPage(new UserDetailsClient.MainPage()); } diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 589c3b7..4755f70 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -24,7 +24,7 @@ async void OnSignInSignOut(object sender, EventArgs e) { if (btnSignInSignOut.Text == "Sign in") { - var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(customizeInteractive: (builder) => + var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(App.Scopes, customizeInteractive: (builder) => { builder.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount); }).ConfigureAwait(false); diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/UserDetailsClient.Droid.csproj b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/UserDetailsClient.Droid.csproj index b785fae..93b5c75 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/UserDetailsClient.Droid.csproj +++ b/2-With-broker/UserDetailsClient/UserDetailsClient.Droid/UserDetailsClient.Droid.csproj @@ -41,6 +41,7 @@ false false false + arm64-v8a;x86;armeabi-v7a pdbonly diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 85f66b1..55d06dd 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -31,10 +31,9 @@ public static void CreatePublicClient() break; } - PCAHelper.Init(App.ClientID, App.Scopes, redirectUri); + PCAHelper.Init(App.ClientID, redirectUri); if (Device.RuntimePlatform == Device.UWP) { - PCAHelper.Instance.PCABuilder.WithExperimentalFeatures(); PCAHelper.Instance.PCABuilder.WithDefaultRedirectUri(); } PCAHelper.Instance.PCABuilder.WithBroker(); @@ -86,7 +85,7 @@ private async void btnSignInSignOutWithBroker_Clicked(object sender, EventArgs e { if (PCAHelper.Instance.AuthResult == null) { - await PCAHelper.Instance.EnsureAuthenticatedAsync(preferredAccount:(accounts) => accounts.FirstOrDefault()).ConfigureAwait(false); + await PCAHelper.Instance.EnsureAuthenticatedAsync(App.Scopes, preferredAccount:(accounts) => accounts.FirstOrDefault()).ConfigureAwait(false); if (PCAHelper.Instance.AuthResult != null) { @@ -111,7 +110,10 @@ private async void btnSignInSignOutWithBroker_Clicked(object sender, EventArgs e } catch (Exception ex) { - await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss").ConfigureAwait(false); + Device.BeginInvokeOnMainThread(async () => + { + await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss").ConfigureAwait(false); + }); } } } diff --git a/Helper/IPCAHelper.cs b/Helper/IPCAHelper.cs index d747615..a2121b7 100644 --- a/Helper/IPCAHelper.cs +++ b/Helper/IPCAHelper.cs @@ -41,25 +41,25 @@ public interface IPCAHelper /// This stores the authentication result, from the auth process. /// When the process starts, it is set to null. /// - AuthenticationResult AuthResult { get; } - + AuthenticationResult AuthResult { get; } + /// /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt acquire token interactively. /// Interactive attempt is optional. /// It provides optional delegates to customize behavior. /// - /// Determines whether to execute AcquireTokenSilent - /// Determines whether to execute AcquireTokenInteractive. By detault, UI interaction takes place if silent action fails. + /// The desired scope + /// TenantID for the token request in case of Multi tenant app /// Function that determines the account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. /// Authenitcation result Task EnsureAuthenticatedAsync( - bool doSilent = true, - bool doInteractive = true, - Func, IAccount> preferredAccount = null, - Action customizeSilent = null, - Action customizeInteractive = null); + string[] scopes, + string tenantID = null, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null); /// /// This will remove all the accounts. diff --git a/Helper/PCAHelper.cs b/Helper/PCAHelper.cs index 1c82192..96003ab 100644 --- a/Helper/PCAHelper.cs +++ b/Helper/PCAHelper.cs @@ -10,7 +10,8 @@ namespace Microsoft.Identity.Client.Helper /// This helper class encapsulates the calling patterns used in the Public Client Applications. /// At the same time, developers can customizes its behaviors /// 1. PublicClientApplicationBuilder PCABuilder - is created in Init and is available to customizes before accessing PCA - /// 2. EnsureAuthenticatedAsync has delegates to customize AcquireTokenInteractiveParameterBuilder, AcquireTokenSilentParameterBuilder and selection of account + /// 2. EnsureAuthenticatedAsync has delegates to customize AcquireTokenInteractiveParameterBuilder, + /// AcquireTokenSilentParameterBuilder and selection of account /// before excute is called. /// public class PCAHelper : IPCAHelper @@ -62,9 +63,6 @@ public IPublicClientApplication PCA /// public AuthenticationResult AuthResult { get; internal set; } - // desired scopes - private string[] _scopes; - /// /// Initializes the PCAHelpr or its derived class as per the generics and PublicClientApplicationBuilder with the given parameters /// PublicClientApplicationBuilder can be customized after this method prior to accessing PublicClientApplication. @@ -72,20 +70,44 @@ public IPublicClientApplication PCA /// /// Any class that is inherited from PCAHelper /// Client id of your application - /// The desired scope /// If you are using recommended pattern fo rredirect Uri, this is optional + /// Authority to acquire token. If this is supplied with tenantID, tenantID need not be supplied as parameter. + /// TenantID required for single tenant app. + /// To use broker or not. Recommended practice true for security. + /// Perform customization after the creation and before execute. /// Creates a new instance irrespective of the existance of the previous instance /// Instance of class inherited from PCAHelper - public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) + public static IPCAHelper Init(string clientId, + string specialRedirectUri = null, + string authority = null, + string tenantId = null, + bool useBroker = true, + Action postInit = null, + bool forceCreate = false) where T : PCAHelper, new() { if (Instance == null || forceCreate) { var pcaHelper = new T(); - pcaHelper._scopes = scopes; pcaHelper.PCABuilder = PublicClientApplicationBuilder.Create(clientId) .WithRedirectUri(specialRedirectUri ?? $"msal{clientId}://auth") - .WithIosKeychainSecurityGroup("com.microsoft.adalcache"); + .WithIosKeychainSecurityGroup("com.microsoft.adalcache") + .WithBroker(useBroker); + if (!string.IsNullOrEmpty(tenantId)) + { + pcaHelper.PCABuilder.WithTenantId(tenantId); + } + + if (!string.IsNullOrEmpty(authority)) + { + pcaHelper.PCABuilder.WithAuthority(authority); + } + + if (postInit != null) + { + postInit(pcaHelper); + } + Instance = pcaHelper; } return Instance; @@ -96,15 +118,15 @@ public static IPCAHelper Init(string clientId, string[] scopes, string specia /// Interactive attempt is optional. /// It provides optional delegates to customize behavior. /// - /// Determines whether to execute AcquireTokenSilent - /// Determines whether to execute AcquireTokenInteractive. By detault, UI interaction takes place if silent action fails. + /// The desired scope + /// TenantID for the token request in case of Multi tenant app /// Function that determines the account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. /// Authenitcation result public async Task EnsureAuthenticatedAsync( - bool doSilent = true, - bool doInteractive = true, + string[] scopes, + string tenantID = null, Func, IAccount> preferredAccount = null, Action customizeSilent = null, Action customizeInteractive = null) @@ -113,56 +135,53 @@ public async Task EnsureAuthenticatedAsync( try { - if (doSilent) + IAccount account = null; + IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + if (preferredAccount != null) { - IAccount account = null; - IEnumerable accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); - if (preferredAccount != null) - { - account = preferredAccount(accounts); - } - else if(accounts != null) - { - account = accounts.FirstOrDefault(); - } - - // Customize silentBuilder - var silentparamsBuilder = PCA.AcquireTokenSilent(_scopes, account); - if (customizeSilent != null) - { - customizeSilent(silentparamsBuilder); - } - - AuthResult = await silentparamsBuilder.ExecuteAsync() - .ConfigureAwait(false); + account = preferredAccount(accounts); } - else if (doInteractive) + else if(accounts != null) { - await AcquireInteractive(customizeInteractive).ConfigureAwait(false); + account = accounts.FirstOrDefault(); } - else + + // Customize silentBuilder + var silentparamsBuilder = PCA.AcquireTokenSilent(scopes, account); + if (tenantID != null) { - throw new ArgumentException($"Both doSilent and do Interactive cannot be false"); + silentparamsBuilder.WithTenantId(tenantID); } + + if (customizeSilent != null) + { + customizeSilent(silentparamsBuilder); + } + + AuthResult = await silentparamsBuilder.ExecuteAsync() + .ConfigureAwait(false); } catch (MsalUiRequiredException) { - if (doInteractive) - { - await AcquireInteractive(customizeInteractive).ConfigureAwait(false); - } + await AcquireInteractive(scopes, tenantID, customizeInteractive).ConfigureAwait(false); } return AuthResult; } // acquire interactively. - private async Task AcquireInteractive(Action customizeInteractive) + private async Task AcquireInteractive(string[] scopes, + string tenantID, + Action customizeInteractive) { try { - var builder = PCA.AcquireTokenInteractive(_scopes) + var builder = PCA.AcquireTokenInteractive(scopes) .WithParentActivityOrWindow(ParentWindow); + if (tenantID != null) + { + builder.WithTenantId(tenantID); + } if (!IsUWP) { @@ -214,7 +233,7 @@ public async Task SignOutAsync() /// Message that needs token public void AddAuthenticationBearerToken(HttpRequestMessage message) { - message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AuthResult.AccessToken); + message.Headers.Add("Authorization", AuthResult.CreateAuthorizationHeader()); } } } From 6ff974b089319adc2e985055049afd1de8113c66 Mon Sep 17 00:00:00 2001 From: SameerK-MSFT <83378772+SameerK-MSFT@users.noreply.github.com> Date: Sat, 15 Jan 2022 18:31:03 -0800 Subject: [PATCH 8/8] Renamed API. Updated doc --- .../UserDetailsClient/MainPage.xaml.cs | 2 +- .../UserDetailsClient/MainPage.xaml.cs | 3 +- Helper/IPCAHelper.cs | 8 +- Helper/PCA Helper.md | 235 ++++++++++-------- Helper/PCAHelper.cs | 18 +- 5 files changed, 146 insertions(+), 120 deletions(-) diff --git a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 4755f70..575aced 100644 --- a/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/1-Basic/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -24,7 +24,7 @@ async void OnSignInSignOut(object sender, EventArgs e) { if (btnSignInSignOut.Text == "Sign in") { - var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync(App.Scopes, customizeInteractive: (builder) => + var authResult = await PCAHelper.Instance.AcquireTokenAsync(App.Scopes, customizeInteractive: (builder) => { builder.WithAuthority(AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount); }).ConfigureAwait(false); diff --git a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs index 55d06dd..7368d7b 100644 --- a/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs +++ b/2-With-broker/UserDetailsClient/UserDetailsClient/MainPage.xaml.cs @@ -36,7 +36,6 @@ public static void CreatePublicClient() { PCAHelper.Instance.PCABuilder.WithDefaultRedirectUri(); } - PCAHelper.Instance.PCABuilder.WithBroker(); } private void UpdateUserContent(string content) @@ -85,7 +84,7 @@ private async void btnSignInSignOutWithBroker_Clicked(object sender, EventArgs e { if (PCAHelper.Instance.AuthResult == null) { - await PCAHelper.Instance.EnsureAuthenticatedAsync(App.Scopes, preferredAccount:(accounts) => accounts.FirstOrDefault()).ConfigureAwait(false); + await PCAHelper.Instance.AcquireTokenAsync(App.Scopes, preferredAccount:(accounts) => accounts.FirstOrDefault()).ConfigureAwait(false); if (PCAHelper.Instance.AuthResult != null) { diff --git a/Helper/IPCAHelper.cs b/Helper/IPCAHelper.cs index 17012cd..19aac28 100644 --- a/Helper/IPCAHelper.cs +++ b/Helper/IPCAHelper.cs @@ -46,8 +46,8 @@ public interface IPCAHelper AuthenticationResult AuthResult { get; } /// - /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt acquire token interactively. - /// Interactive attempt is optional. + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException + /// attempt acquire token interactively. /// It provides optional delegates to customize behavior. /// /// The desired scope @@ -55,8 +55,8 @@ public interface IPCAHelper /// Function that determines the account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. - /// Authenitcation result - Task EnsureAuthenticatedAsync( + /// Authentication result + Task AcquireTokenAsync( string[] scopes, string tenantID = null, Func, IAccount> preferredAccount = null, diff --git a/Helper/PCA Helper.md b/Helper/PCA Helper.md index 02bd66c..7b7e9bf 100644 --- a/Helper/PCA Helper.md +++ b/Helper/PCA Helper.md @@ -1,105 +1,132 @@ -# PCA Helper -PCA developers use common pattern to acquire token. The code appears to be repeatative and very granular. At the same time, the current API is based on the builder pattern. There are several "With" APIs and a some are used in commonly in the Public Client Application (PCA). Due to the abudance of the With APIs, the learning curve can be high to perform simple/common tasks. - -PCAHelper extracts API at a higher level offering flexibility for granular programming as desired. The helper provides methods with optional paramaeters and lambdas for customization. It has the following features: - -- It comes with two main common practices as default: - - There is only one instance of PCA - - Scope for permissions is defined only once. - The above default can be customized as desired. - - It is inteface based. So the calling app can create Mocks and perform testing w/o havinng dependency on MSAL.NET and any network connectivity - - It allows customization of token acqusition methods. - - It allows specialization of the Helper class should developer choose it. - -# APIs -The APIs are briefly described here. - -## Initialization -This can be done once in the App. This initializes the helper with client id and scope, if it does not have a standard redirect URI, it can be customized here. By default the PCAHelper is a singleton. It can be overwritten by forcing creation. - -```CSharp -public static IPCAHelper Init(string clientId, string[] scopes, string specialRedirectUri = null, bool forceCreate = false) - where T : PCAHelper, new() -``` - -Example usage: -```CSharp -// initialize with the client id and scopes. One can optionally pass special redirect URI -// else it creates one with commonly used: $"msal{clientId}://auth" -PCAHelper.Init(B2CConstants.ClientID, B2CConstants.Scopes); -// additional customization of the builder -PCAHelper.Instance.PCABuilder.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp); -``` - -## Obtain the token -This API is for obtaining the token. It attempts to acquire a token silently and if that fails, presents an interactive sign-in dialogue to the user. It provides options to do silent, interactive and has the ability to customize each parameter builder. -One can also choose the preferred account. - -``` CSharp -public async Task EnsureAuthenticatedAsync( - bool doSilent = true, - bool doInteractive = true, - Func, IAccount> preferredAccount = null, - Action customizeSilent = null, - Action customizeInteractive = null) -``` - -Example usage (B2C sample): -``` CSharp -var authResult = await PCAHelper.Instance.EnsureAuthenticatedAsync( - doSilent:false, - preferredAccount: (accounts) => GetAccountByPolicy(accounts, B2CConstants.PolicyEditProfile), - customizeInteractive: (builder) => - { - builder.WithPrompt(Prompt.NoPrompt) - .WithAuthority(B2CConstantsAuthorityEditProfile); - }).ConfigureAwait(false); -``` - -## Use the token -User can sign a request without having to deal with the token. - -``` CSharp -public void AddAuthenticationBearerToken(HttpRequestMessage message) -``` - -## Sign out -Here is the API to sign out - -``` CSharp -public async Task SignOutAsync() -``` - -# Properties -Apart from the above, the API provides Instance of the PCHelper that has properties to do more granular programming: - -``` CSharp - - /// - /// IPublicClientApplication that is created on the first get - /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get - /// - IPublicClientApplication PCA { get; } - - /// - /// Application builder. It is created in Init and this member can be used to customize it before Build occurs in PCA->get - /// - PublicClientApplicationBuilder PCABuilder { get; } - - /// - /// This is applicable to Android. Please update this property in MainActivity.Create method - /// and consequently with change in the current activity. - /// - object ParentWindow { get; set; } - - /// - /// In UWP app, set it to true. - /// - bool IsUWP { get; set; } - - /// - /// This stores the authentication result, from the auth process. - /// When the process starts, it is set to null. - /// - AuthenticationResult AuthResult { get; } +# PCA Helper +The utility provides easy to use API and flexibility for granular programming for authentication in public client application. This is achieved by encapsulating the common patterns and providing optional delegates for more granular programming. The APIs accepts have various commonly used values making it configurable. + +It has the following features: +- The API is interface based. Developer can create Mocks and perform testing with no dependency on MSAL.NET. + +- The initialization allows specialization of the PCAHelper class should developer choose it. +- It is Singleton by default, it can be overridden. +- API provides commonly used values as defaults, reducing the burden on the developers. +- It provides delegates for customization allowing granular programming. + +# APIs +The APIs are briefly described here. Developer will first need to initialize the utility and then call API to acquire token. + +## Initialization +Initialization can be done in the App class. It instantiates the helper with the client id and various optional parameters. By default, the PCAHelper is a singleton. It can be overwritten by forcing creation. Other actions such as logging, token cache initialization can be done optionally in the postInit action. + +```CSharp + + /// + /// Instantiates the PCAHelper (or its derived class) and PublicClientApplicationBuilder with the given parameters. + /// PublicClientApplicationBuilder can be customized after this method or in the postInit prior to accessing PublicClientApplication. + /// By default it is singleton pattern with option to force creation. + /// + /// Any class that is inherited from PCAHelper + /// Client id of your application + /// If you are using recommended pattern for redirect Uri (i.e. $"msal{clientId}://auth"), this is optional + /// Authority to acquire token. If this is supplied with tenantID, tenantID need not be supplied as parameter. + /// TenantID - This is required for single tenant app. + /// To use broker or not. Recommended practice is to use it for security. + /// Perform customization after the creation and before execute. + /// Creates a new instance irrespective of the existance of the previous instance + /// Instance of class inherited from PCAHelper + public static IPCAHelper Init(string clientId, + string specialRedirectUri = null, + string authority = null, + string tenantId = null, + bool useBroker = true, + Action postInit = null, + bool forceCreate = false) + where T : PCAHelper, new() +``` + +Example usage: +```CSharp +// initialize with the client id and redirectURI. One can optionally pass the other parameters +PCAHelper.Init(App.ClientID, redirectUri); +// additional customization of the builder +PCAHelper.Instance.PCABuilder.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp); +``` + +## Acquire the token +AcquireTokenAsync will acquire authentication token given the scopes. It attempts to acquire the token silently and if that fails, it presents an interactive sign-in dialogue to the user. Developer can provide tenantID in case of multi-tenant application. It also has optional delegates to select the preferred account, customize silent and interactive acquisition. + +``` CSharp + /// + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException + /// attempt acquire token interactively. + /// It provides optional delegates to customize behavior. + /// + /// The desired scope + /// TenantID for the token request in case of Multi tenant app + /// Function that determines the account to be used. The default is first. (optional) + /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. + /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. + /// Authentication result + Task AcquireTokenAsync( + string[] scopes, + string tenantID = null, + Func, IAccount> preferredAccount = null, + Action customizeSilent = null, + Action customizeInteractive = null); +``` + +Example usage (B2C sample): +``` CSharp +var authResult = PCAHelper.Instance.AcquireTokenAsync(App.Scopes, + preferredAccount:(accounts) => GetAccountByPolicy(accounts, B2CConstants.PolicyEditProfile), + customizeInteractive: (builder) => + { + builder.WithPrompt(Prompt.NoPrompt) + .WithAuthority(B2CConstantsAuthorityEditProfile); + }).ConfigureAwait(false); +``` + +## Use the token +User can sign a request without having to deal with the token. + +``` CSharp +public void AddAuthenticationBearerToken(HttpRequestMessage message) +``` + +## Sign out +Here is the API to sign out + +``` CSharp +public async Task SignOutAsync() +``` + +# Properties +Apart from the above, the API provides Instance of the PCHelper that has properties to do more granular programming: + +``` CSharp + + /// + /// IPublicClientApplication that is created on the first get + /// If you want to customize PublicClientApplicationBuilder, please do it before calling the first get + /// + IPublicClientApplication PCA { get; } + + /// + /// Application builder. It is created in Init and this member can be used to customize it before Build occurs in PCA->get + /// + PublicClientApplicationBuilder PCABuilder { get; } + + /// + /// This is applicable to Android. Please update this property in MainActivity.Create method + /// and consequently with change in the current activity. + /// + object ParentWindow { get; set; } + + /// + /// In UWP app, set it to true. + /// + bool IsUWP { get; set; } + + /// + /// This stores the authentication result, from the auth process. + /// When the process starts, it is set to null. + /// + AuthenticationResult AuthResult { get; } ``` \ No newline at end of file diff --git a/Helper/PCAHelper.cs b/Helper/PCAHelper.cs index 96003ab..d360020 100644 --- a/Helper/PCAHelper.cs +++ b/Helper/PCAHelper.cs @@ -64,16 +64,16 @@ public IPublicClientApplication PCA public AuthenticationResult AuthResult { get; internal set; } /// - /// Initializes the PCAHelpr or its derived class as per the generics and PublicClientApplicationBuilder with the given parameters - /// PublicClientApplicationBuilder can be customized after this method prior to accessing PublicClientApplication. + /// Instantiates the PCAHelpr (or its derived class) and PublicClientApplicationBuilder with the given parameters. + /// PublicClientApplicationBuilder can be customized after this method or in the postInit prior to accessing PublicClientApplication. /// By default it is singleton pattern with option to force creation. /// /// Any class that is inherited from PCAHelper /// Client id of your application - /// If you are using recommended pattern fo rredirect Uri, this is optional + /// If you are using recommended pattern for redirect Uri (i.e. $"msal{clientId}://auth"), this is optional /// Authority to acquire token. If this is supplied with tenantID, tenantID need not be supplied as parameter. - /// TenantID required for single tenant app. - /// To use broker or not. Recommended practice true for security. + /// TenantID - This is required for single tenant app. + /// To use broker or not. Recommended practice is to use for security. /// Perform customization after the creation and before execute. /// Creates a new instance irrespective of the existance of the previous instance /// Instance of class inherited from PCAHelper @@ -114,8 +114,8 @@ public static IPCAHelper Init(string clientId, } /// - /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException attempt acquire token interactively. - /// Interactive attempt is optional. + /// This encapuslates the common pattern to acquire token i.e. attempt AcquireTokenSilent and if that throws MsalUiRequiredException + /// attempt acquire token interactively. /// It provides optional delegates to customize behavior. /// /// The desired scope @@ -123,8 +123,8 @@ public static IPCAHelper Init(string clientId, /// Function that determines the account to be used. The default is first. (optional) /// This is a delegate to optionally customize AcquireTokenSilentParameterBuilder. /// This is a delegate to optionally customize AcquireTokenInteractiveParameterBuilder. - /// Authenitcation result - public async Task EnsureAuthenticatedAsync( + /// Authentication result + public async Task AcquireTokenAsync( string[] scopes, string tenantID = null, Func, IAccount> preferredAccount = null,