Tutorial: Sign in users in .NET MAUI shell app
This tutorial demonstrates how to add sign-in and sign-out code in .NET Multi-platform App UI (.NET MAUI) and run the shell app on the Android platform.
In this tutorial, you learn how to:
- Add sign-in and sign-out code.
- Modify the app Shell.
- Add platform-specific code.
- Add app settings.
- Run and test .NET MAUI shell app.
Prerequisites
- Tutorial: Create a .NET MAUI shell app, add MSALClient, and include an image resource
- Tutorial: Register and configure .NET MAUI mobile app in a customer tenant
Add sign-in and sign-out code
The user interface (UI) of a .NET MAUI app is constructed of objects that map to the native controls of each target platform. The main control groups used to create the UI of a .NET MAUI app are pages, layouts, and views.
Add main view page
The next steps will organize our code so that the main view
is defined.
Delete MainPage.xaml and MainPage.xaml.cs from your project, they're no longer needed. In the Solution Explorer pane, find the entry for MainPage.xaml, right-click it and select Delete.
Right-click on the SignInMaui project and select Add > New Folder. Name the folder Views.
Right-click on the Views.
Select Add > New Item....
Select .NET MAUI in the template list.
Select the .NET MAUI ContentPage (XAML) template. Name the file MainView.xaml.
Select Add.
The MainView.xaml file will open in a new document tab, displaying all of the XAML markup that represents the UI of the page. Replace the XAML markup with the following markup:
<?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="SignInMaui.Views.MainView" Title="Microsoft identity platform" > <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center"> <Image Source="azure_active_directory.png" SemanticProperties.Description="Azure Active Directory Logo" HeightRequest="200" HorizontalOptions="Center" /> <Label Text="Azure AD for Customers" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" SemanticProperties.HeadingLevel="Level1" FontSize="26" HorizontalOptions="Center" /> <Button x:Name="SignInButton" Text="Sign In" SemanticProperties.Hint="Sign In" Clicked="OnSignInClicked" HorizontalOptions="Center" IsEnabled="False"/> </VerticalStackLayout> </ScrollView> </ContentPage>
Save the file.
Let's break down the key parts of the XAML controls placed on the page:
<ContentPage>
is the root object for the MainView class.<VerticalStackLayout>
is the child object of the ContentPage. This layout control arranges its children vertically, one after the other.<Image>
displays an image, in this case it's using the azure_active_directory.png that you downloaded earlier.<Label>
controls display text.<Button>
can be pressed by the user, which raises theClicked
event. You can run code in response to theClicked
event.Clicked="OnSignInClicked"
theClicked
event of the button is assigned to theOnSignInClicked
event handler, which will be defined in the code-behind file. You'll create this code in the next step.
Handle the OnSignInClicked event
The next step is to add the code for the button's Clicked
event.
In the Solution Explorer pane of Visual Studio, expand the MainView.xaml file to reveal its code-behind file MainView.xaml.cs. Open the MainView.xaml.cs and replace the content of the file with following code:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views { public partial class MainView : ContentPage { public MainView() { InitializeComponent(); IAccount cachedUserAccount = PublicClientSingleton.Instance.MSALClientHelper.FetchSignedInUserFromCache().Result; _ = Dispatcher.DispatchAsync(async () => { if (cachedUserAccount == null) { SignInButton.IsEnabled = true; } else { await Shell.Current.GoToAsync("claimsview"); } }); } private async void OnSignInClicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); await Shell.Current.GoToAsync("claimsview"); } protected override bool OnBackButtonPressed() { return true; } } }
The
MainView
class is a content page responsible for displaying the main view of the app. In the constructor, it retrieves the cached user account using theMSALClientHelper
from thePublicClientSingleton
instance and enables the sign-in button, if no cached user account is found.When the sign-in button is clicked, it calls the
AcquireTokenSilentAsync
method to acquire a token silently and navigates to theclaimsview
page using theShell.Current.GoToAsync
method. Additionally, theOnBackButtonPressed
method is overridden to return true, indicating that the back button is disabled for this view.
Add claims view page
The next steps will organize the code so that ClaimsView
page is defined. The page will display the user's claims found in the ID token.
In the Solution Explorer pane of Visual Studio, right-click on the Views.
Select Add > New Item....
Select .NET MAUI in the template list.
Select the .NET MAUI ContentPage (XAML) template. Name the file ClaimsView.xaml.
Select Add.
The ClaimsView.xaml file will open in a new document tab, displaying all of the XAML markup that represents the UI of the page. Replace the XAML markup with the following markup:
<?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="SignInMaui.Views.ClaimsView" Title="ID Token View"> <Shell.BackButtonBehavior> <BackButtonBehavior IsVisible="False" IsEnabled="False" /> </Shell.BackButtonBehavior> <VerticalStackLayout> <Label Text="Azure AD for Customers" FontSize="26" HorizontalOptions="Center" /> <Label Text="MAUI sample" FontSize="26" Padding="0,0,0,20" HorizontalOptions="Center" /> <Label Padding="0,20,0,0" VerticalOptions="Center" HorizontalOptions="Center" FontSize="18" Text="Claims found in ID token" /> <ListView ItemsSource="{Binding IdTokenClaims}" x:Name="Claims"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid Padding="0, 0, 0, 0"> <Label Grid.Column="1" Text="{Binding}" HorizontalOptions="Center" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button x:Name="SignOutButton" Text="Sign Out" HorizontalOptions="Center" Clicked="SignOutButton_Clicked" /> </VerticalStackLayout> </ContentPage>
This XAML markup code represents the UI layout for a claim view in a .NET MAUI app. It starts by defining the
ContentPage
with a title and disabling the back button behavior.Inside a
VerticalStackLayout
, there are severalLabel
elements displaying static text, followed by aListView
namedClaims
that binds to a collection calledIdTokenClaims
to display the claims found in the ID token. Each claim is rendered within aViewCell
using aDataTemplate
and displayed as a centeredLabel
within a Grid.Lastly, there's a
Sign Out
button centered at the bottom of the layout, which triggers theSignOutButton_Clicked
event handler when clicked.
Handle the ClaimsView data
The next step is to add the code to handle ClaimsView
data.
In the Solution Explorer pane of Visual Studio, expand the ClaimsView.xaml file to reveal its code-behind file ClaimsView.xaml.cs. Open the ClaimsView.xaml.cs and replace the content of the file with following code:
using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui.Views; public partial class ClaimsView : ContentPage { public IEnumerable<string> IdTokenClaims { get; set; } = new string[] {"No claims found in ID token"}; public ClaimsView() { BindingContext = this; InitializeComponent(); _ = SetViewDataAsync(); } private async Task SetViewDataAsync() { try { _ = await PublicClientSingleton.Instance.AcquireTokenSilentAsync(); IdTokenClaims = PublicClientSingleton.Instance.MSALClientHelper.AuthResult.ClaimsPrincipal.Claims.Select(c => c.Value); Claims.ItemsSource = IdTokenClaims; } catch (MsalUiRequiredException) { await Shell.Current.GoToAsync("claimsview"); } } protected override bool OnBackButtonPressed() { return true; } private async void SignOutButton_Clicked(object sender, EventArgs e) { await PublicClientSingleton.Instance.SignOutAsync().ContinueWith((t) => { return Task.CompletedTask; }); await Shell.Current.GoToAsync("mainview"); } }
The ClaimsView.xaml.cs code represents the code-behind for a claim view in a .NET MAUI app. It starts by importing the necessary namespaces and defining the
ClaimsView
class, which extendsContentPage
. TheIdTokenClaims
property is an enumerable of strings, initially set to a single string indicating no claims found.The
ClaimsView
constructor sets the binding context to the current instance, initializes the view components, and calls theSetViewDataAsync
method asynchronously. TheSetViewDataAsync
method attempts to acquire a token silently, retrieves the claims from the authentication result, and sets theIdTokenClaims
property to display them in theListView
namedClaims
. If aMsalUiRequiredException
occurs, indicating that user interaction is needed for authentication, the app navigates to the claims view.The
OnBackButtonPressed
method overrides the back button behavior to always return true, preventing the user from going back from this view. TheSignOutButton_Clicked
event handler signs the user out using thePublicClientSingleton
instance, and upon completion, navigates to themain view
.
Modify the app Shell
The AppShell
class defines an app's visual hierarchy, the XAML markup used in creating the UI of the app. Update the AppShell
to let it know about the Views
.
Double-click the
AppShell.xaml
file in the Solution Explorer pane to open the XAML editor. Replace the XAML markup with the following code:<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="SignInMaui.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:SignInMaui.Views" Shell.FlyoutBehavior="Disabled"> <ShellContent Title="Home" ContentTemplate="{DataTemplate local:MainView}" Route="MainPage" /> </Shell>
The XAML code defines an
AppShell
class that disables the flyout behavior and sets the main content to aShellContent
element with a titleHome
and a content template pointing to theMainView
class.In the Solution Explorer pane of Visual Studio, expand the AppShell.xaml file to reveal its code-behind file AppShell.xaml.cs. Open the AppShell.xaml.cs and replace the content of the file with following code:
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using SignInMaui.Views; namespace SignInMaui; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute("mainview", typeof(MainView)); Routing.RegisterRoute("claimsview", typeof(ClaimsView)); } }
You update the
AppShell.xaml.cs
file to include the necessary route registrations for theMainView
andClaimsView
. By calling theInitializeComponent()
method, you ensure the initialization of theAppShell
class. TheRegisterRoute()
method associate themainview
andclaimsview
routes with their respective view types,MainView
andClaimsView
.
Add platform-specific code
A .NET MAUI app project contains a Platforms folder, with each child folder representing a platform that .NET MAUI can target. To provide Android application-specific behavior to supplement the default application class, you follow these steps:
Double-click
Platforms/Android/AndroidManifest.xml
file in the Solution Explorer pane to open the xml editor. Update the following properties:- Set Application name to MAUI CIAM.
- Set Package name to SignInMaui.Droid.
- Set Minimum Android version to Android 5.0 (API level 21).
Double-click
Platforms/Android/MainActivity.cs
file in the Solution Explorer pane to open the csharp editor. Replace the content of the file with following code:// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Android.App; using Android.Content; using Android.Content.PM; using Android.OS; using SignInMaui.MSALClient; using Microsoft.Identity.Client; namespace SignInMaui; [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] public class MainActivity : MauiAppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // configure platform specific params PlatformConfig.Instance.RedirectUri = $"msal{PublicClientSingleton.Instance.MSALClientHelper.AzureAdConfig.ClientId}://auth"; PlatformConfig.Instance.ParentWindow = this; // Initialize MSAL and platformConfig is set _ = Task.Run(async () => await PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()).Result; } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data); } }
Let's break down the key parts of the code you have added:
- The necessary
using
statements are included at the top. - The
MainActivity
class is defined, inheriting fromMauiAppCompatActivity
, which is the base class for the Android platform in .NET MAUI. - The [Activity] attribute is applied to the
MainActivity
class, specifying various settings for the Android activity.Theme = "@style/Maui.SplashTheme"
sets the splash theme for the activity.MainLauncher = true
designates this activity as the main entry point of the application.ConfigurationChanges
specifies the configuration changes that the activity can handle, such as screen size, orientation, UI mode, screen layout, smallest screen size, and density.
OnCreate
method is overridden to provide custom logic when the activity is being created.base.OnCreate(savedInstanceState)
calls the base implementation of the method.PlatformConfig.Instance.RedirectUri
is set to a dynamically generated value based onPublicClientSingleton.Instance.MSALClientHelper.AzureAdConfig.ClientId
. It configures the redirect URI for the MSAL client.PlatformConfig.Instance.ParentWindow
is set to the current activity instance, which specifies the parent window for authentication-related operations.PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()
initializes the MSAL client app asynchronously using a helper method from a singleton instance calledMSALClientHelper
. TheTask.Run
is used to execute the initialization on a background thread, and.Result
is used to synchronously wait for the task to complete.
OnActivityResult
method is overridden to handle the result of an activity launched by the current activity.base.OnActivityResult(requestCode, resultCode, data)
calls the base implementation of the method.AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data)
sets the authentication continuation event arguments based on the received request code, result code, and intent data. This is used to continue the authentication flow after an external activity returns a result.
- The necessary
In the Solution Explorer pane of Visual Studio, select Platforms.
Right-click on the Android folder > Add > New Item....
Select C# Items > Class. Name the file
MsalActivity.cs
.Replace the content of
MsalActivity.cs
file with the following code:// 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 Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using Microsoft.Identity.Client; namespace MauiAppBasic.Platforms.Android.Resources { [Activity(Exported =true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataHost = "auth", DataScheme = "msalEnter_the_Application_Id_Here")] public class MsalActivity : BrowserTabActivity { } }
Let's break down the key parts of the code you have added:
MsalActivity
class is declared within theMauiAppBasic.Platforms.Android.Resources
namespace. The class inherits from theBrowserTabActivity
class, indicating that it extends its functionality.- The class is decorated with the
[Activity(Exported = true)]
attribute, which signifies that the activity is exported and can be accessed by other methods. - An intent filter is specified using the "[IntentFilter(...)]" attribute. It configures the activity to intercept the
ActionView
intent. - The intent filter is set to handle the
ActionView
intent with the specifiedDataScheme
(msalEnter_the_Application_Id_Here
) andDataHost
("auth"). This configuration allows the activity to handle the authentication process by intercepting and processing theActionView
intent. ReplaceEnter_the_Application_Id_Here
with the Application (client) ID of the app you registered earlier.
Add app settings
Settings allow the separation of data that configures the behavior of an app from the code, allowing the behavior to be changed without rebuilding the app. The MauiAppBuilder
provides ConfigurationManager
to configure settings in our .NET MAUI app. Let's add the appsettings.json
file as an EmbeddedResource
.
To create appsettings.json
, follow these steps:
In the Solution Explorer pane of Visual Studio, right-click on the SignInMaui project > Add > New Item....
Select Web > JavaScript JSON Configuration File. Name the file
appsettings.json
.Select Add.
Select appsettings.json
In the Properties pane, set Build Action to Embedded resource.
In the Properties pane, set Copy to Output Directory to Copy always.
Replace the content of
appsettings.json
file with the following code:{ "AzureAd": { "Authority": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/", "ClientId": "Enter_the_Application_Id_Here", "CacheFileName": "msal_cache.txt", "CacheDir": "C:/temp" }, "DownstreamApi": { "Scopes": "openid offline_access" } }
In the
appsettings.json
, find the placeholder:Enter_the_Tenant_Subdomain_Here
and replace it with the Directory (tenant) subdomain. For example, if your tenant primary domain iscontoso.onmicrosoft.com
, usecontoso
. If you don't have your tenant name, learn how to read your tenant details.Enter_the_Application_Id_Here
and replace it with the Application (client) ID of the app you registered earlier.
Run and test .NET MAUI mobile app
.NET MAUI apps are designed to run on multiple operating systems and devices. You'll need to select which target you want to test and debug your app with.
Set the Debug Target in the Visual Studio toolbar to the device you want to debug and test with. The following steps demonstrate setting the Debug Target to Android:
- Select Debug Target drop-down.
- Select Android Emulators.
- Select emulator device.
Run the app by pressing F5 or select the play button at the top of Visual Studio.
You can now test the sample .NET MAUI Android app. After you run the app, the Android app window appears in an emulator:
On the Android window that appears, select the Sign In button. A browser window opens, and you're prompted to sign in.
During the sign in process, you're prompted to grant various permissions (to allow the application to access your data). Upon successful sign in and consent, the application screen displays the main page.
Next Steps
Feedback
Submit and view feedback for