diff --git a/MAUI/MauiAppB2C/App.xaml b/MAUI/MauiAppB2C/App.xaml new file mode 100644 index 0000000..f82e11f --- /dev/null +++ b/MAUI/MauiAppB2C/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/MAUI/MauiAppB2C/App.xaml.cs b/MAUI/MauiAppB2C/App.xaml.cs new file mode 100644 index 0000000..a5ccd60 --- /dev/null +++ b/MAUI/MauiAppB2C/App.xaml.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +namespace MauiB2C; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + + MainPage = new AppShell(); + } +} diff --git a/MAUI/MauiAppB2C/AppShell.xaml b/MAUI/MauiAppB2C/AppShell.xaml new file mode 100644 index 0000000..dd6266e --- /dev/null +++ b/MAUI/MauiAppB2C/AppShell.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/MAUI/MauiAppB2C/AppShell.xaml.cs b/MAUI/MauiAppB2C/AppShell.xaml.cs new file mode 100644 index 0000000..1c2e3a5 --- /dev/null +++ b/MAUI/MauiAppB2C/AppShell.xaml.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +namespace MauiB2C; + +public partial class AppShell : Shell +{ + public AppShell() + { + InitializeComponent(); + } +} diff --git a/MAUI/MauiAppB2C/MSALClient/B2CConstants.cs b/MAUI/MauiAppB2C/MSALClient/B2CConstants.cs new file mode 100644 index 0000000..2c25dce --- /dev/null +++ b/MAUI/MauiAppB2C/MSALClient/B2CConstants.cs @@ -0,0 +1,19 @@ +namespace MauiB2C.MSALClient +{ + public static class B2CConstants + { + // Azure AD B2C Coordinates + public const string Tenant = "fabrikamb2c.onmicrosoft.com"; + public const string AzureADB2CHostname = "fabrikamb2c.b2clogin.com"; + public const string ClientID = "e5737214-6372-472c-a85a-68e8fbe6cf3c"; + public static readonly string RedirectUri = $"msal{ClientID}://auth"; + public const string PolicySignUpSignIn = "b2c_1_susi"; + + public static readonly string[] Scopes = { "https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read" }; + + public static readonly string AuthorityBase = $"https://{AzureADB2CHostname}/tfp/{Tenant}/"; + public static readonly string AuthoritySignInSignUp = $"{AuthorityBase}{PolicySignUpSignIn}"; + + public const string IOSKeyChainGroup = "com.microsoft.adalcache"; + } +} diff --git a/MAUI/MauiAppB2C/MSALClient/PCAWrapperB2C.cs b/MAUI/MauiAppB2C/MSALClient/PCAWrapperB2C.cs new file mode 100644 index 0000000..d2c68b2 --- /dev/null +++ b/MAUI/MauiAppB2C/MSALClient/PCAWrapperB2C.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using Microsoft.Identity.Client; +using Microsoft.IdentityModel.Abstractions; + +namespace MauiB2C.MSALClient +{ + /// + /// This is a wrapper for PublicClientApplication. It is singleton. + /// + public class PCAWrapperB2C + { + /// + /// This is the singleton used by ux. Since PCAWrapper constructor does not have perf or memory issue, it is instantiated directly. + /// + public static PCAWrapperB2C Instance { get; private set; } = new PCAWrapperB2C(); + + /// + /// Instance of PublicClientApplication. It is provided, if App wants more customization. + /// + internal IPublicClientApplication PCA { get; } + + // private constructor for singleton + private PCAWrapperB2C() + { + // Create PCA once. Make sure that all the config parameters below are passed + PCA = PublicClientApplicationBuilder + .Create(B2CConstants.ClientID) + .WithExperimentalFeatures() // this is for upcoming logger + .WithLogging(_logger) + .WithB2CAuthority(B2CConstants.AuthoritySignInSignUp) + .WithIosKeychainSecurityGroup(B2CConstants.IOSKeyChainGroup) + .WithRedirectUri(B2CConstants.RedirectUri) + .Build(); + } + + /// + /// Acquire the token silently + /// + /// desired scopes + /// Authentication result + public async Task AcquireTokenSilentAsync(string[] scopes) + { + // Get accounts by policy + IEnumerable accounts = await PCA.GetAccountsAsync(B2CConstants.PolicySignUpSignIn).ConfigureAwait(false); + + AuthenticationResult authResult = await PCA.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) + .WithB2CAuthority(B2CConstants.AuthoritySignInSignUp) + .ExecuteAsync() + .ConfigureAwait(false); + + return authResult; + } + + /// + /// Perform the interactive acquisition of the token for the given scope + /// + /// desired scopes + /// + internal async Task AcquireTokenInteractiveAsync(string[] scopes) + { + return await PCA.AcquireTokenInteractive(B2CConstants.Scopes) + .WithParentActivityOrWindow(PlatformConfig.Instance.ParentWindow) + .ExecuteAsync() + .ConfigureAwait(false); + } + + /// + /// It will sign out the user. + /// + /// + internal async Task SignOutAsync() + { + var accounts = await PCA.GetAccountsAsync().ConfigureAwait(false); + foreach (var acct in accounts) + { + await PCA.RemoveAsync(acct).ConfigureAwait(false); + } + } + + // Custom logger for sample + private MyLogger _logger = new MyLogger(); + + // Custom logger class + private class MyLogger : IIdentityLogger + { + /// + /// Checks if log is enabled or not based on the Entry level + /// + /// + /// + public bool IsEnabled(EventLogLevel eventLogLevel) + { + //Try to pull the log level from an environment variable + var msalEnvLogLevel = Environment.GetEnvironmentVariable("MSAL_LOG_LEVEL"); + + EventLogLevel envLogLevel = EventLogLevel.Informational; + Enum.TryParse(msalEnvLogLevel, out envLogLevel); + + return envLogLevel <= eventLogLevel; + } + + /// + /// Log to console for demo purpose + /// + /// Log Entry values + public void Log(LogEntry entry) + { + Console.WriteLine(entry.Message); + } + } + + } +} diff --git a/MAUI/MauiAppB2C/MSALClient/PlatformConfig.cs b/MAUI/MauiAppB2C/MSALClient/PlatformConfig.cs new file mode 100644 index 0000000..395179e --- /dev/null +++ b/MAUI/MauiAppB2C/MSALClient/PlatformConfig.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MauiB2C.MSALClient +{ + /// + /// Platform specific configuration. + /// + public class PlatformConfig + { + /// + /// Instance to store data + /// + public static PlatformConfig Instance { get; } = new PlatformConfig(); + + /// + /// Platform specific Redirect URI + /// + public string RedirectUri { get; set; } = $"msal{B2CConstants.ClientID}://auth"; + + /// + /// Platform specific parent window + /// + public object ParentWindow { get; set; } + + // private constructor to ensure singleton + private PlatformConfig() + { + } + } +} diff --git a/MAUI/MauiAppB2C/MainPage.xaml b/MAUI/MauiAppB2C/MainPage.xaml new file mode 100644 index 0000000..a43f328 --- /dev/null +++ b/MAUI/MauiAppB2C/MainPage.xaml @@ -0,0 +1,46 @@ + + + + + + + + +