Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Del MAUI apps re-added with 4.50 #223

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions MAUI/MauiAppB2C/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiB2C"
x:Class="MauiB2C.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
13 changes: 13 additions & 0 deletions MAUI/MauiAppB2C/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
14 changes: 14 additions & 0 deletions MAUI/MauiAppB2C/AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MauiB2C.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiB2C"
Shell.FlyoutBehavior="Disabled">

<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />

</Shell>
11 changes: 11 additions & 0 deletions MAUI/MauiAppB2C/AppShell.xaml.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
19 changes: 19 additions & 0 deletions MAUI/MauiAppB2C/MSALClient/B2CConstants.cs
Original file line number Diff line number Diff line change
@@ -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";
}
}
117 changes: 117 additions & 0 deletions MAUI/MauiAppB2C/MSALClient/PCAWrapperB2C.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// This is a wrapper for PublicClientApplication. It is singleton.
/// </summary>
public class PCAWrapperB2C
{
/// <summary>
/// This is the singleton used by ux. Since PCAWrapper constructor does not have perf or memory issue, it is instantiated directly.
/// </summary>
public static PCAWrapperB2C Instance { get; private set; } = new PCAWrapperB2C();

/// <summary>
/// Instance of PublicClientApplication. It is provided, if App wants more customization.
/// </summary>
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();
}

/// <summary>
/// Acquire the token silently
/// </summary>
/// <param name="scopes">desired scopes</param>
/// <returns>Authentication result</returns>
public async Task<AuthenticationResult> AcquireTokenSilentAsync(string[] scopes)
{
// Get accounts by policy
IEnumerable<IAccount> accounts = await PCA.GetAccountsAsync(B2CConstants.PolicySignUpSignIn).ConfigureAwait(false);

AuthenticationResult authResult = await PCA.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp)
.ExecuteAsync()
.ConfigureAwait(false);

return authResult;
}

/// <summary>
/// Perform the interactive acquisition of the token for the given scope
/// </summary>
/// <param name="scopes">desired scopes</param>
/// <returns></returns>
internal async Task<AuthenticationResult> AcquireTokenInteractiveAsync(string[] scopes)
{
return await PCA.AcquireTokenInteractive(B2CConstants.Scopes)
.WithParentActivityOrWindow(PlatformConfig.Instance.ParentWindow)
.ExecuteAsync()
.ConfigureAwait(false);
}

/// <summary>
/// It will sign out the user.
/// </summary>
/// <returns></returns>
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
{
/// <summary>
/// Checks if log is enabled or not based on the Entry level
/// </summary>
/// <param name="eventLogLevel"></param>
/// <returns></returns>
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<EventLogLevel>(msalEnvLogLevel, out envLogLevel);

return envLogLevel <= eventLogLevel;
}

/// <summary>
/// Log to console for demo purpose
/// </summary>
/// <param name="entry">Log Entry values</param>
public void Log(LogEntry entry)
{
Console.WriteLine(entry.Message);
}
}

}
}
37 changes: 37 additions & 0 deletions MAUI/MauiAppB2C/MSALClient/PlatformConfig.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Platform specific configuration.
/// </summary>
public class PlatformConfig
{
/// <summary>
/// Instance to store data
/// </summary>
public static PlatformConfig Instance { get; } = new PlatformConfig();

/// <summary>
/// Platform specific Redirect URI
/// </summary>
public string RedirectUri { get; set; } = $"msal{B2CConstants.ClientID}://auth";

/// <summary>
/// Platform specific parent window
/// </summary>
public object ParentWindow { get; set; }

// private constructor to ensure singleton
private PlatformConfig()
{
}
}
}
46 changes: 46 additions & 0 deletions MAUI/MauiAppB2C/MainPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiB2C.MainPage">

<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">

<Image
Source="dotnet_bot.png"
SemanticProperties.Description="Cute dot net bot waving hi to you!"
HeightRequest="200"
HorizontalOptions="Center" />

<Label
Text="Hello, World!"
SemanticProperties.HeadingLevel="Level1"
FontSize="32"
HorizontalOptions="Center" />

<Label
Text="Welcome to .NET Multi-platform App UI"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center" />

<Button
x:Name="SignInButton"
Text="Sign in"
SemanticProperties.Hint="Sign In"
Clicked="OnSignInClicked"
HorizontalOptions="Center" />

<Button
x:Name="SignOutButton"
Text="Sign Out"
HorizontalOptions="Center"
Clicked="SignOutButton_Clicked"/>
</VerticalStackLayout>
</ScrollView>

</ContentPage>
78 changes: 78 additions & 0 deletions MAUI/MauiAppB2C/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using MauiB2C.MSALClient;
using Microsoft.Identity.Client;
using System.Text;

namespace MauiB2C;

public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}

private async void OnSignInClicked(object sender, EventArgs e)
{
try
{
// First attempt silent login, which checks the cache for an existing valid token.
// If this is very first time or user has signed out, it will throw MsalUiRequiredException
AuthenticationResult result = await PCAWrapperB2C.Instance.AcquireTokenSilentAsync(B2CConstants.Scopes).ConfigureAwait(false);

string claims = GetClaims(result);

// show the claims
await ShowMessage("AcquireTokenTokenSilent call Claims", claims).ConfigureAwait(false);
}
catch (MsalUiRequiredException)
{
// This executes UI interaction to obtain token
AuthenticationResult result = await PCAWrapperB2C.Instance.AcquireTokenInteractiveAsync(B2CConstants.Scopes).ConfigureAwait(false);

string claims = GetClaims(result);

// show the Claims
await ShowMessage("AcquireTokenInteractive call Claims", claims).ConfigureAwait(false);
}
catch (Exception ex)
{
await ShowMessage("Exception in AcquireTokenTokenSilent", ex.Message).ConfigureAwait(false);
}
}

private static string GetClaims(AuthenticationResult result)
{
StringBuilder sb = new StringBuilder();
foreach (var claim in result.ClaimsPrincipal.Claims)
{
sb.Append("Claim Type = ");
sb.Append(claim.Type);
sb.Append(" Value = ");
sb.AppendLine(claim.Value);
}

return sb.ToString();
}

private async void SignOutButton_Clicked(object sender, EventArgs e)
{
_ = await PCAWrapperB2C.Instance.SignOutAsync().ContinueWith(async (t) =>
{
await ShowMessage("Signed Out", "Sign out complete").ConfigureAwait(false);
}).ConfigureAwait(false);
}

// display the message
private Task ShowMessage(string title, string message)
{
_ = this.Dispatcher.Dispatch(async () =>
{
await DisplayAlert(title, message, "OK").ConfigureAwait(false);
});

return Task.CompletedTask;
}
}

Loading