简体   繁体   中英

Xamarin Forms Android - Authentication Failure on Deployment using MSAL Library

Development Information - Xamarin Forms Mobile application, utilizes MSAL library version 4.35.0 to authenticate against Azure AD and uses Brokered authentication flow utilizing the Microsoft Authenticator. This is coded in Visual Studio 2019 using C# and.Net 5.

Problem - Everything works in the Android emulator, but once deployed to an actual device, using Company Portal (Intune), the authentication piece fails with message:

Authentication Error [Android broker] The broker redirect URI is incorrect, it should be msauth://com.xxxxxx.xxxxxxx/xxxxxxxxxxxxxx Please visit https://aka.ms/Brokered-Authentication-for-Android for more details

I compared the redirect uri in the Azure portal to the one being displayed in the error message and they don't match, I don't know where it's getting this redirect uri value from?? Everything in the code base uses the callback uri specified in the Azure portal

I've worked through multiple MSDN documents, download example projects from GitHub, modified the Android Manifest file, etc. None of that seems to fix this issue. I am at my wits end with this. Here is an example of the Authentication code:

        public static IPublicClientApplication PCA;

        //OAuthSettings is a class containing my values to pass to the methods of the 
        //PublicClientApplicationBuilder
        var builder = PublicClientApplicationBuilder
                            .Create(OAuthSettings.ApplicationId)
                            .WithTenantId(OAuthSettings.TenantId)
                            .WithBroker()
                            .WithRedirectUri(OAuthSettings.RedirectUri);

        PCA = builder.Build();

        try
        {
            var accounts = await PCA.GetAccountsAsync();

            var silentAuthResult = await PCA
                .AcquireTokenSilent(new string[] { "api://xxxxxxxxxxxxxx/.default" }, accounts.FirstOrDefault())
                .ExecuteAsync();

            AccessToken = new JwtSecurityToken(silentAuthResult.AccessToken);

            //more code removed for brevity
        }
        catch (MsalUiRequiredException msalEx)
        {
            
            var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();

            // Prompt the user to sign-in
            var interactiveRequest = PCA.AcquireTokenInteractive(new string[] { "api://xxxxxxxxxxxxxxxxxxx/.default" });

            //Used for Android and iOS
            AuthUIParent = windowLocatorService?.GetCurrentParentWindow();

            if (AuthUIParent != null)
            {
                interactiveRequest = interactiveRequest
                    .WithParentActivityOrWindow(AuthUIParent);
            }
            //

            var interactiveAuthResult = await interactiveRequest.ExecuteAsync();

            AccessToken = new JwtSecurityToken(interactiveAuthResult.AccessToken);
       }

Android Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" package="com.gpdgroup.GPDMobileAppTest" android:installLocation="auto" android:versionCode="7">
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="mycompany.Android" android:theme="@style/MainTheme" android:usesCleartextTraffic="true" android:icon="@mipmap/icon" android:roundIcon="@mipmap/icon">
        <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" />
                <data android:scheme="msal{clientID}" android:host="auth" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />               
                <data android:scheme="msauth" android:host="com.mycompany.myapp" android:path="/{base64 hash}" />
            </intent-filter>
        </activity>
    </application>
    <!--Necessary to fix issue on authentication for level 30-->
    <queries>
        <package android:name="com.azure.authenticator" />
        <package android:name="com.mycompany.myapp" />
        <package android:name="com.microsoft.windowsintune.companyportal" />
        <!-- Required for API Level 30 to make sure the app detect browsers
        (that don't support custom tabs) -->
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" />
        </intent>
        <!-- Required for API Level 30 to make sure the app can detect browsers that support custom tabs -->
        <!-- https://developers.google.com/web/updates/2020/07/custom-tabs-android-11#detecting_browsers_that_support_custom_tabs -->
        <intent>
            <action android:name="android.support.customtabs.action.CustomTabsService" />
        </intent>
    </queries>
</manifest>
  

Screenshot of Azure Portal:

在此处输入图像描述

I also added an MSAL Authentication JSON file to the Resources folder of the Android project in a sub folder called raw called msal_default_config.json:

{ 
   "client_id": "xxxxxxxxxxxxxxxxxxxxx", 
   "redirect_uri": "msauth://com.mycompany.myapp/{base64 url encoded signature hash}", 
   "broker_redirect_uri_registered": true, 
   "account_mode" : "SINGLE", 
   "authorities": [ 
   { "type": "AAD", "audience": { "type": "AzureADandPersonalMicrosoftAccount", 
     "tenant_id": "xxxxxxxxxxxxxxxxxx" } 
   } ] 
}

And I also have this class for the Android project which inherits BrowserTabActivity class, called MsalActivity:

[Activity]
[IntentFilter(new[] { Intent.ActionView },
   Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault },
   DataHost = "auth",
   DataScheme = "msal{clientID}")]

public class MsalActivity : BrowserTabActivity
{
}

The way Google has architected the mechanism through which one app can invoke another app mandates that the redirect URI has to include a signature of the application pkg.

The problem is that every Android SDK deployment has its own signature. So when you develop the app, you have one signature. When another developer tries to build and deploy, it's another signature. And when you create the official app that you submit, well there's another signature as well. They all use different Android SDKs.

SO you need to register 1 redirect URI for each app developer AND 1 redirect URI for the packaged bits.

You should expect that future versions of your application will have the same signature, so this process is done only once.

So after multiple changes and trying different things, we got it working, but we had to do the following:

  1. I had my co-worker who manages the Play Store, create a new Application for release. (Not sure if this was necessary)

  2. I changed the package name of the Android application to be all lower case. (Not sure if this was necessary)

  3. Used the following to get the production signing key hash so that we could add it to the Azure portal. Production Signing Hash

How we got the production signature hash :

A. My co-worker who manages the Play Store, gave me the Hexadecmial SHA-1 value of the Application Signing from Google Play for the newly created application

B. Used the answer by Sujeet Kumar to get the base64 hash by feeding the Hexadecimal value into Chrome's console window with the following:

btoa('{your hexadecimal value goes here without the curly brackets}'.split(':')
.map(hc => String.fromCharCode(parseInt(hc, 16))).join(''))

C. Took this base64 hash and put it under the application Android callback uri section of the Azure portal

Since no one gave me this last step of how to get the production signature hash, I thought it may be helpful to put the steps I used to get it here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM