AzureAD/azure-activedirectory-library-for-dotnet

Android App crash after selecting account (using broker)

Closed this issue · 27 comments

I have developed an Android app using Xamarin Forms and implemented ADAL for authentication. This works fine, but after enabling Conditional Access, we have to use a broker instead of authenticating using a WebView. So I followed all the steps mentioned on the Wiki (https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/leveraging-brokers-on-Android-and-iOS):

  • Enable support for Broker on Your AuthencationContext
  • Ensure your app has access to the Broker accounts
  • Update the application manifest
  • Register a Redirect URI for your application
  • Update MainActivity.cs to call back ADAL.NET

ADAL version: 5.2.4

Code for acquiring an access token:

public AuthenticationResult authResult { get; private set; }

    public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
    {
        var context = new AuthenticationContext(authority);
        var param = new PlatformParameters(CrossCurrentActivity.Current.Activity, true, PromptBehavior.SelectAccount);
        
        if (context.TokenCache.ReadItems().Count() > 0)
            context = new AuthenticationContext(context.TokenCache.ReadItems().First().Authority);

        try
        {
            authResult = await context.AcquireTokenAsync(resource, clientId, new Uri(returnUri), param, userId).ConfigureAwait(false);
            return authResult;
        }
        catch
        {
            return null;
        }
    }

App behaviour:

Upon app launch, first time it asks for the needed permissions and after that it does show an account picker where I am able to select my account. So this makes me believe it gets te account from the broker. But after selecting my account, the app crashes. In the logs the following is shown:

[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] System.NullReferenceException: Object reference not set to an instance of an object
[MonoDroid] at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x0001c] in <41e5a54c101e43dca8a2f462dab041fa>:0
[MonoDroid] at IWe.Droid.MainActivity.OnActivityResult (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x00009] in :0
[MonoDroid] at Android.App.Activity.n_OnActivityResult_IILandroid_content_Intent_ (System.IntPtr jnienv, System.IntPtr native__this, System.Int32 requestCode, System.Int32 native_resultCode, System.IntPtr native_data) [0x00014] in :0
[MonoDroid] at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.6(intptr,intptr,int,int,intptr)
[iwetes] JNI RegisterNativeMethods: attempt to register 0 native methods for android.runtime.JavaProxyThrowable
[AndroidRuntime] Shutting down VM
[AndroidRuntime] FATAL EXCEPTION: main
[AndroidRuntime] Process: <my_packageid>, PID: 23545
[AndroidRuntime] android.runtime.JavaProxyThrowable: System.NullReferenceException: Object reference not set to an instance of an object
[AndroidRuntime] at Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x0001c] in <41e5a54c101e43dca8a2f462dab041fa>:0
[AndroidRuntime] at IWe.Droid.MainActivity.OnActivityResult (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x00009] in :0
[AndroidRuntime] at Android.App.Activity.n_OnActivityResult_IILandroid_content_Intent_ (System.IntPtr jnienv, System.IntPtr native__this, System.Int32 requestCode, System.Int32 native_resultCode, System.IntPtr native_data) [0x00014] in :0
[AndroidRuntime] at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.6(intptr,intptr,int,int,intptr)
[AndroidRuntime] at md5b4882cfeb0ca027f9661a63d1a4e24bc.MainActivity.n_onActivityResult(Native Method)
[AndroidRuntime] at md5b4882cfeb0ca027f9661a63d1a4e24bc.MainActivity.onActivityResult(MainActivity.java:65)
[AndroidRuntime] at android.app.Activity.dispatchActivityResult(Activity.java:7762)
[AndroidRuntime] at android.app.ActivityThread.deliverResults(ActivityThread.java:4603)
[AndroidRuntime] at android.app.ActivityThread.handleSendResult(ActivityThread.java:4652)
[AndroidRuntime] at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
[AndroidRuntime] at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
[AndroidRuntime] at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
[AndroidRuntime] at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1948)
[AndroidRuntime] at android.os.Handler.dispatchMessage(Handler.java:106)
[AndroidRuntime] at android.os.Looper.loop(Looper.java:214)
[AndroidRuntime] at android.app.ActivityThread.main(ActivityThread.java:7050)
[AndroidRuntime] at java.lang.reflect.Method.invoke(Native Method)
[AndroidRuntime] at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
[AndroidRuntime] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
[Process] Sending signal. PID: 23545 SIG: 9

I am having a hard time figuring out what is going on and what I might be doing wrong or forget. Any help is appreciated.

Thanks @pbruinink did you update the AppDelegate class as well?

Can you also send us the broker logs? In authenticator app, under help, hit "send logs" and then send us the incident id, so we can take a look on the broker side of things...you can email the # if you want: jeferrie@microsoft.com

Thanks.

Thanks for your reply @jennyf19. It's a Xamarin Forms Android-only app, so the AppDelegate class isn't available. I'll send the broker logs and email the inc number.

Thanks

@pbruinink Do you happen to have a repro you can share?

@jennyf19 send you a simple example project via email.

@pbruinink do you have battery optimization turned on the device? if so, that will restrict the communication with the broker. Based on the logs, we don't see any acquire token calls reaching broker, there are just bind failures between key transfer communication between two brokers, which indicates the device might be in power optimization mode. Could you check and let us know? thank you.

@jennyf19 the device (Samsung s8) is set to a ‘high performance’ powermode and also the ‘optimize battery usage’ option is not enabled for both our app and the microsoft authenticator app.

Hi @jennyf19: I'm glad to tell you that I was able to fix this Issue. It turned out I had to change the redirectUri. When creating a release in our Managed Google Playstore the APK package is re-signed with another certificate and thus the base64 encoded signature in the redirectUri also had to be changed. Now the broker is correctly called and the user can select their account.

One question remains though: When the app is launched, it always shows the Android account picker, which I believe is being triggered by the following code (which must be implemented when using a broker):

        string WORK_AND_SCHOOL_TYPE = "com.microsoft.workaccount";
        // See the Android docs for customizing the UI https://developers.google.com/android/reference/com/google/android/gms/common/AccountPicker
        Intent intent = AccountManager.NewChooseAccountIntent(null, null, new[] { WORK_AND_SCHOOL_TYPE }, null, null, null, null);
        // Start an activity with this intent, e.g. 
        CrossCurrentActivity.Current.Activity.StartActivity(intent);

        // this variable should now have all the accounts in the broker
        var accManager = AccountManager.Get(Application.Context);
        var accounts = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE);

After selecting the account in the Android account picker, the user is prompted with the broker’s ’Select account’ account picker is shown (only when we cannot perform a silent login) which is actually used to authenticate the user. It is mentioned that the above code should be implemented, but it always shows the Android account picker pop-up when the app is launched (even if ADAL can silently authenticate the user) which is very annoying. Could you tell me how to fix that unwanted experience?

@pbruinink I'm glad to hear you got it working.

After selecting the account in the Android account picker, the user is prompted with the broker’s ’Select account’ account picker is shown (only when we cannot perform a silent login) which is actually used to authenticate the user. It is mentioned that the above code should be implemented, but it always shows the Android account picker pop-up when the app is launched (even if ADAL can silently authenticate the user) which is very annoying. Could you tell me how to fix that unwanted experience?

@iambmelt @@biozal any thoughts on the above?

@pbruinink I'm glad to hear you got it working.

After selecting the account in the Android account picker, the user is prompted with the broker’s ’Select account’ account picker is shown (only when we cannot perform a silent login) which is actually used to authenticate the user. It is mentioned that the above code should be implemented, but it always shows the Android account picker pop-up when the app is launched (even if ADAL can silently authenticate the user) which is very annoying. Could you tell me how to fix that unwanted experience?

@iambmelt @@biozal any thoughts on the above?

Not super clear on the use case here but FWIW ADAL can be seeded with a login_hint which will auto-select the designated account if it's available.

Part I'm unclear on is why AccountManager needs to be queried directly; ADAL provides an API for getting broker users via AuthenticationContext.getBrokerUsers()

@iambmelt:

Part I'm unclear on is why AccountManager needs to be queried directly; ADAL provides an API for getting broker users via AuthenticationContext.getBrokerUsers()

Well I'm just following the instructions mentioned on the page below:

https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/leveraging-brokers-on-Android-and-iOS#update-appdelegate-to-ensure-that-the-broker-calls-back-adalnet

Ensure your app has access to the Broker accounts:

Google has introduced an extra security measure for apps wanting to access accounts. ADAL needs to list all the "Microsoft" accounts, i.e. all the accounts created by the broker app. Currently, it is your responsibility to ensure your app has access to this - by invoking the Android Account Picker.

string WORK_AND_SCHOOL_TYPE = "com.microsoft.workaccount";
// See the Android docs for customizing the UI https://developers.google.com/android/reference/com/google/android/gms/common/AccountPicker
Intent intent = AccountManager.NewChooseAccountIntent(null, null, new[] { WORK_AND_SCHOOL_TYPE }, null, null, null, null);
// Start an activity with this intent, e.g.
CrossCurrentActivity.Current.Activity.StartActivity(intent);

// this variable should now have all the accounts in the broker
var accounts = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE);

After adding the code above in the MainActivity, the Android Account Picker is always shown when the app is launched. It will show the account of the user and it doesn't matter if you select it or select cancel as it doesn't seem to do anything. After selecting either the account or the cancel button, the ADAL account picker appears (again showing the users account) and after selecting it, we are able to authenticate correctly. But the Android account picker showing up is very disruptive.

@biozal do you want to propose something for the wiki? thanks so much for the input.

@biozal: Thanks for your response. It looks like I've implemented it in the same way:

AndroidManifest.xml:

 <!-- Required for Broker usage with ADAL -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

MainActivity.cs:

 public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }

 // Used for ADAL callback
 protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        base.OnActivityResult(requestCode, resultCode, data);

        AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
    }

  // required for broker support with API > 22
    private void RequestPermissions()
    {
        this.RequestPermissions(new string[] { Manifest.Permission.GetAccounts }, 0);
        this.RequestPermissions(new string[] { Manifest.Permission.ManageAccounts }, 1);
        this.RequestPermissions(new string[] { Manifest.Permission.UseCredentials }, 2);
    }

 private void enableBroker()
    {
        // adding support for using the broker when using ADAL

        string WORK_AND_SCHOOL_TYPE = "com.microsoft.workaccount";
        // See the Android docs for customizing the UI https://developers.google.com/android/reference/com/google/android/gms/common/AccountPicker
        Intent intent = AccountManager.NewChooseAccountIntent(null, null, new[] { WORK_AND_SCHOOL_TYPE }, null, null, null, null);
        // Start an activity with this intent, e.g. 
        CrossCurrentActivity.Current.Activity.StartActivity(intent);

        // this variable should now have all the accounts in the broker
        var accManager = AccountManager.Get(Application.Context);
        var accounts = accManager.GetAccountsByType(WORK_AND_SCHOOL_TYPE);
    }

 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);

        Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this, savedInstanceState);
        RequestPermissions();
        if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.GetAccounts) == (int)Android.Content.PM.Permission.Granted)
        {
            enableBroker();
        }
        LoadApplication(new App());
 }

The actual authentication using the broker is working fine, it's just that the Android Account Picker appears before the broker seems to get involved. Am I missing something or do I have to move the 'enableBroker' method to somewhere else?

Any idea what differs in my code (shown above) as apposed to yours? Or any suggestion on what to change/add to avoid the Android Account Picker showing up each time?

@iambmelt your thoughts on the above?

here a simple example app:
https://github.com/pbruinink/adaltest

@biozal Did you manage to have a look at the example code yet?

Okay no problem, hope you are feeling well again.
Thanks for your support, it is much appreciated! Hope you can tell me if I am doing something wrong or else what is causing this behaviour and how I can fix it.

@biozal any update please?

@biozal have you been able to check the example code yet?

@jennyf19 Can you have a look at the example code (https://github.com/pbruinink/adaltest)?
I just can't figure out why the Android account picker always pops up twice before the ADAL broker asks to select an account.

@pbruinink sure. I'll try to take a look at this in the next day or two. thanks for your patience.

@jennyf19 thanks. I really appreciate that!

@biozal thanks, that would be appreciated