User Authentication in Xamarin with Azure AD B2C
Published: 2020-09-09 21:23:53

In this post we'll examine what you need to do to implement Azure AD B2C authentication with your Xamarin.Forms apps. We did this live on our stream so if you prefer to watch the on demand video, you can watch it [here](https://www.twitch.tv/videos/735042968) or YouTube below:

{% youtube wNpKgMY6P6w %}

If you feel that Xamarin.iOS and Xamarin.Android is something we should do, go ahead and let us know in the comments or tweet us at [@christosmatskas](https://twitter.com/christosmatskas) and/or [@AzureAndChill](https://twitter.com/AzureAndChill) :)

# Prerequisites
- Azure Subscription (get a FREE subscription [here](https://azure.microsoft.com/free))
- An Azure AD B2C tenant (spin one using this [doc](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/authentication/azure-ad-b2c#configure-an-azure-active-directory-b2c-tenant))
- Visual Studio or Visual Studio for Mac (we'll use VS4Mac in this instance)
- Xamarin with Android and iOS dependencies so that you can build and debug locally ([install Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/))

# Create your App Registration
To be able to authenticate and manage users in our Xamarin app we need to create an app registration in our AAD B2C. This will define how users sign in and sign up to our app and provide the appropriate settings for our app. Let's navigate to the B2C portal and create a new App Registration

![Create app registration](https://dev-to-uploads.s3.amazonaws.com/i/ga2nho4dd4cmpvcgpqh2.png)

We need to give it a meaningful name and press **Register**
![Register new app](https://dev-to-uploads.s3.amazonaws.com/i/pb4w3br7fgv7271fpl69.png)

Next, we open the app registration and navigate to the **Authentication** tab to add a new platform

![Add platform](https://dev-to-uploads.s3.amazonaws.com/i/skzurm0kjys68q87e232.png)

Since we are doing a mobile app, we choose Mobile and then we make sure to check the Redirect URI for MSAL as per the images below:

![Select Mobile](https://dev-to-uploads.s3.amazonaws.com/i/mcrpxxuvvocipi5ny3ec.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/agipsoiqudwrsryafkbs.png)

Note that the Redirect URI will be specific to your app. 

# Configure the User Flow
User flows are used by B2C to define how we want our users to interact with our authentication platform. They determine which social providers to use (if any), how to sign up for the first time and how to enable our users to manager their accounts. I like to use the default flows as they tend to meet my needs 90% of the time but it's nice to know that you can go totally crazy and create custom flows to serve edge cases. 

In this instance, we're using a default Sign In Sign Up flow. To do this, we need to navigate back to the root of our B2C tenant and open the **User flows** tab and click on the **New user flow** button

![User Flow](https://dev-to-uploads.s3.amazonaws.com/i/zot9l1ymo7lp2hka65an.png)

From the available flows we want to select the **Sign up and sign in** flow and make sure to go with the **Recommended** settings before pressing the **Create** button.

![Sign up and Sign in flow](https://dev-to-uploads.s3.amazonaws.com/i/otnlaxdvwe5mbunt1e1x.png)

We need to provide a tenant-unique (unique name for our B2C tenant) name and then configure the identity providers. The default is username and password but we can add other social media identity providers like Google, Twitter or Github as we see fit. We can also add any other custom OIDC identity provider so we are not confined to the predefined ones - joy!

![Flow Config](https://dev-to-uploads.s3.amazonaws.com/i/jeohvxxfdhank85hfhb0.png)

At the bottom of the page, we need to configure the attributes that we want to collect and return during sign up and sign in. For this, we need to press **Show more...** and choose the right attributes to collect and return as claims and press **Ok** to persist our changes.

![App claims](https://dev-to-uploads.s3.amazonaws.com/i/i7f9uflcn8j1zs7tqhqd.png)

We are now good to press **Create** to save our new user flow.

At this point, we need to collect some information that we will use in our Xamarin app to configure MSAL (the auth library)

From the app registration, we need:
- The application (client) Id: 0b3eee66-0000-0000-0000-d9318c4f589d
- The tenant name: `youB2CTenantName`
- The tenant Id: `yourB2CTenantName.onmicrosoft.com`
- The Redirect URI: msal0b3eee66-0000-0000-0000-d9318c4f589d://auth
- The use flow name: `b2c_1_sisu_xamarin` (lowercase better)

At this point we're done with the B2C configuration and portal and we can start writing some code! One down, one to go

#Add AAD B2C authentication in Xamarin.Forms

For this post I'll be starting with a fresh, vanilla Xamarin.Forms project. In VS4Mac, I create a brand new, empty Xamarin.Forms project using the **File -> New Project** option

![File, New Project](https://dev-to-uploads.s3.amazonaws.com/i/23yndm2dp0an7h6vdhe1.png)

Then I give it a meaningful name and I make sure that both iOS and Android are selected:

![Add project name](https://dev-to-uploads.s3.amazonaws.com/i/qxvuzpsdngxfl7qebycv.png)

I also made sure that the solution files look right and that git files are added (this is the default behavior anyway but good to check) before pushing the **Create** button

![Select Git options](https://dev-to-uploads.s3.amazonaws.com/i/lbohprhuzg81ujh4jkva.png)

With the solution created, we can start by adding the MSAL library via NuGet. The library will need to be added on all 3 projects, the class library, the iOS and Android

![Launch Solution level NuGet](https://dev-to-uploads.s3.amazonaws.com/i/z08ywhj6fvr83upu25yn.png)

The easiest way is to search for `Microsoft.Identity.Client` and select it in order to be added it to our solution.

![Search for NuGet package](https://dev-to-uploads.s3.amazonaws.com/i/qb758jhfc22a6nttt9aa.png)

Accept the dependencies to complete the installation

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x2pw3eno3vuq26q2h0n4.png)

OK! So now we are ready to start coding...

First, we need to add a new `Constants.cs` class to hold all our configuration settings. Add the following code:

```
public static class Constants
{
    public static readonly string TenantName = "YourTenantName";
    public static readonly string TenantId = "YourTenantName.onmicrosoft.com";
    public static readonly string ClientId = "834a4112-0000-0000-0000-860f082f0c8a";
    public static readonly string SignInPolicy = "b2c_1_sisu_xamarin";
    public static readonly string IosKeychainSecurityGroups = "com.<yourcompany>.<groupname>"; // e.g com.contoso.aadb2cauthentication
    public static readonly string[] Scopes = new string[] { "openid", "offline_access" };
    public static readonly string AuthorityBase = $"https://{TenantName}.b2clogin.com/tfp/{TenantId}/";
    public static readonly string AuthoritySignIn = $"{AuthorityBase}{SignInPolicy}";
}
```
We now need to add some code in the `App.xaml.cs` to configure the authentication as per the code below:

```
using Microsoft.Identity.Client;
using Xamarin.Forms;

namespace XamarinDemoWithB2C
{
    public partial class App : Application
    {
        public static IPublicClientApplication AuthenticationClient { get; private set; }

        public static object UIParent { get; set; } = null;

        public App()
        {
            InitializeComponent();

            AuthenticationClient = PublicClientApplicationBuilder.Create(Constants.ClientId)
               .WithIosKeychainSecurityGroup(Constants.IosKeychainSecurityGroups)
               .WithB2CAuthority(Constants.AuthoritySignIn)
               .WithRedirectUri($"msal{Constants.ClientId}://auth")
               .Build();

            MainPage = new NavigationPage(new LoginPage());
        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }
}
```
The `AuthenticationClient` is what we will call in other pages to authenticate users. We also expose a `UIParent` property which needs to be populated with the a content page when we work with Android. For the iOS app it can be null.

At this point the code will not compile as we reference a `LoginPage` that doesn't exist yet. Let's add this. The `LoginPage` should be a `ContentPage` that contains the following XAML

```
<ContentPage.Content>
    <StackLayout Margin="30,40,30,0" HorizontalOptions="Center" VerticalOptions="Center" >
    <Button x:Name="SignInBtn" Text="Sign in" Clicked="OnSignInClicked"/>
    </StackLayout>
</ContentPage.Content>
```
As you can see, we only have a `Sign in` button - pretty bare but functional if you ask me. 

We now need to configure the SignIn functionality in the code behind, i.e `LoginPage.xaml.cs` as per below:

```
using System;
using System.Linq;
using Microsoft.Identity.Client;
using Xamarin.Forms;

namespace XamarinDemoWithB2C
{
    public partial class LoginPage : ContentPage
    {
        public LoginPage()
        {
            InitializeComponent();
        }

        protected override async void OnAppearing()
        {
            try
            {
                // Look for existing account
                var accounts = await App.AuthenticationClient.GetAccountsAsync();

                if (accounts.Count() >= 1)
                {
                    AuthenticationResult result = await App.AuthenticationClient
                        .AcquireTokenSilent(Constants.Scopes, accounts.FirstOrDefault())
                        .ExecuteAsync();

                    await Navigation.PushAsync(new LoginResultPage(result));
                }
            }
            catch
            {
                // Do nothing - the user isn't logged in
            }
            base.OnAppearing();
        }

        async void OnSignInClicked(object sender, EventArgs e)
        { 
            AuthenticationResult result;

            try
            {
                result = await App.AuthenticationClient
                    .AcquireTokenInteractive(Constants.Scopes)
                    .WithPrompt(Prompt.ForceLogin)
                    .WithParentActivityOrWindow(App.UIParent)
                    .ExecuteAsync();

                await Navigation.PushAsync(new LoginResultPage(result));
            }
            catch(MsalClientException)
            {

            }
        }
    }
}
```
The code does 2 things. 

1. First, during the `OnAppearing` we check if there are is an existing account in the MSAL cache and we attempt to acquire the id token silently. If this fails, we do nothing as we need the user to interact with the page via the **Sign In** button
2. We then wire up the `SignIn_Clicked` event to interactively authenticate users. 

In both cases, if the authentication is successful, we navigate users to the `LoginResultPage` where we want to display some basic information derived from the ID token claims.

The final piece of the puzzle is the `LoginResultPage`. Let's create this one so that we can display some information from the ID token. In a normal application, this wouldn't be part of the flow. Typically, we retrieve the user information and we then let users continue with rest of the app. However, since this sample is focused only on authentication, the flow is somewhat limited. 

Open the `LoginResultPage.xaml` and add the following XAML to display the information we need and provide a Sign Out button:

```
<ContentPage.Content>
    <StackLayout Margin="0,30,30,0" HorizontalOptions="Center" VerticalOptions="Center">
        <Label x:Name="welcome"/>
        <Label x:Name="issuer"/>
        <Label x:Name="subject"/>
        <Label x:Name="audience"/>
        <Label x:Name="email"/>

        <StackLayout Margin="40,30,40,0" HorizontalOptions="Center" VerticalOptions="Center">
            <Button x:Name="SignOutBtn" Text="Sign out" Clicked="SignOutBtn_Clicked"/>
        </StackLayout>
    </StackLayout>
</ContentPage.Content>
```
In the code behind, add the following code to crack open the ID token and also implement the sign out functionality:

```
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using Microsoft.Identity.Client;
using Xamarin.Forms;

namespace XamarinDemoWithB2C
{
    public partial class LoginResultPage : ContentPage
    {
        private AuthenticationResult authenticationResult;

        public LoginResultPage(AuthenticationResult authResult)
        {
            authenticationResult = authResult;        
            InitializeComponent();
        }

        protected override void OnAppearing()
        {
            GetClaims();
            base.OnAppearing();
        }
        private void GetClaims()
        {
            var token = authenticationResult.IdToken;
            if (token != null)
            {
                var handler = new JwtSecurityTokenHandler();
                var data = handler.ReadJwtToken(authenticationResult.IdToken);
                var claims = data.Claims.ToList();
                if (data != null)
                { 
                    this.welcome.Text = $"Welcome {data.Claims.FirstOrDefault(x => x.Type.Equals("name")).Value}";
                    this.issuer.Text = $"Issuer: { data.Claims.FirstOrDefault(x => x.Type.Equals("iss")).Value}";
                    this.subject.Text = $"Subject: {data.Claims.FirstOrDefault(x => x.Type.Equals("sub")).Value}";
                    this.audience.Text = $"Audience: {data.Claims.FirstOrDefault(x => x.Type.Equals("aud")).Value}";
                    this.email.Text = $"Email: {data.Claims.FirstOrDefault(x => x.Type.Equals("emails")).Value}";
                }
            }
        }

        async void SignOutBtn_Clicked(System.Object sender, System.EventArgs e)
        {
            await App.AuthenticationClient.RemoveAsync(authenticationResult.Account);
            await Navigation.PushAsync(new LoginPage());
        }
    }
}
```
To be able to work with JWT we need to add an extra NuGet package. Open the NuGet package manager, search and add the `System.IdentityModels.Token.Jwt package.

# iOS specific configuration
There are a couple of extra configurations that we need to in order for the application to work. In the iOS project, open the `AppDelegate.cs` class and add the following code

```
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{   
 AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
    return base.OpenUrl(app, url, options);
}
```

# Android specific configuration
For Android to work, there is some more work we need to do. First, open the `AndroidManifest.xml` and update it to look like this

```
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.xamarindemowithb2c">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
    <application android:label="XamarinDemoWithB2C.Android" android:theme="@style/MainTheme">
        <activity android:name="microsoft.identity.client.BrowserTabActivity">  		 
            <intent-filter>
		 <action android:name="android.intent.action.VIEW" />
		 <category android:name="android.intent.category.DEFAULT" />
		 <category android:name="android.intent.category.BROWSABLE" />
				<!-- example -->
				<!-- <data android:scheme="msalaaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" android:host="auth" /> -->
		 <data android:scheme="msal<YourClientId>" android:host="auth" />
	    </intent-filter>
        </activity>"
    </application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
```
Finally, we need to update the `MainActivity.cs` to make it look like this:

```
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Microsoft.Identity.Client;
using Android.Content;

namespace XamarinDemoWithB2C.Droid
{
    [Activity(Label = "XamarinDemoWithB2C", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
            App.UIParent = this;
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);
            AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
        }
    }
}
```
Notably, we added a new method `OnActivityResult()` to handle the authentication redirect and on the `OnCreate` we added this line of code `App.UIParent = this;` as we need to ensure that this property gets populated when we do our authentication.

# Putting it all together
We can now build and run the project using the local emulator or a phone. If everything has been wired correctly you should be able to navigate to the app and authenticate as per the example below:

![running app sample](https://dev-to-uploads.s3.amazonaws.com/i/n4w11iz63sj9bl4k6rv5.png)

If you just want to clone the app and run it on your end, check out the [GitHub repo](https://github.com/cmatskas/XamarinFormsB2CAuth)

# Come code with us
We love streaming and we usually stream live twice a week on Twitch! But what we love more is to collaborate with developers and tackle real world scenarios. As such, if you're interested to code with us, if have questions/challenges that you want us to help you with or simply a suggestion on what to stream, then we would love for you to reach out. Join us live on the [425Show](https://twitch.tv/425Show), follow and bookmark us:

7am - 10am PT / 2pm - 5pm UTC
7am - 9am PT / 2pm - 4pm UTC [Community Hours]

Be sure to send your questions to us here, on Twitter ([Christos](https://twitter.com/christosmatskas), [JP](https://twitter.com/AzureAndChill)) or email: iddevsoc@service.microsoft.com!