Microsoft-authentication-library-for-dotnet: Msal Unity integration(Android)

Created on 16 May 2019  路  17Comments  路  Source: AzureAD/microsoft-authentication-library-for-dotnet

Using Msal in Unity for UI token authentication relies on objects that Unity itself doesn't generate, in the case of Android "Activities". It makes it pretty unfriendly to incorporate into projects.

Ideally a fully in house solution, such as automatically referencing the unity player as the current activity.

Simple clear and concise documentation of how to use Msal with Unity would be really helpful. It seems way more complicated than it needs too, and an almost total lack of documentation makes it incredibly difficult to track and understand issues.

Unity enhancement external

Most helpful comment

For people reading this in future, it's possible to use msal for android in Unity.
1) Include msal as an aar plugin (or you can pull it as a gradle dependency using maintemplate.gradle support in Unity).
2) Make an android library that calls msal and returns the access token as string. Unity supports calling Android classes and returning values from them: https://docs.unity3d.com/ScriptReference/AndroidJavaClass.html, https://docs.unity3d.com/ScriptReference/AndroidJavaProxy.html

For initializing msal you need a reference to the Unity activity. On C# side get Unity activity like this:

AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

Pass this to java plugin and call getApplicationContext() to get the context needed to initialize msal.

All 17 comments

@JolofNar : how can I ramp-up to authentication with Unity (I don't know Unity).
Can you help us help you? do you have repro steps, samples that we could see, etc ...

Hi @jmprieur I'm new to using Github like this, so please be patient with me, but I would love to help however I can.

I think to reproduce you would need to set up a build for android in Unity, and create a simple scene with a button then add this script

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Microsoft.Identity.Client;
using System.Windows;
using Microsoft.Graph;
using System.Linq;
using System.Windows.Forms;

public class AadLoginUI : MonoBehaviour {

;

public Button LoginButton;



public static string ClientId =  // client/app id from azure 
public static string Tenant =  // tenant id 
private string[] scopes = { "user.read" };


// Use this for initialization
void Start () {

    LoginButton.onClick.RemoveAllListeners();
    LoginButton.onClick.AddListener(LoginButton_OnCLick);

}


public void LoginButton_OnCLick()
{
    try
    {

        Debug.Log("In authenticateUser method: pre");
        AuthenticateUser();
        Debug.Log("In authenticateUser method: post");

    }
    catch (System.Exception ex)
    {
        StatusText.text = "An Error has occured";
        Debug.Log("error1 = "ex.Message);
    }
}

public async void AuthenticateUser()
{
    Debug.Log("In authenticateUser method");
    AuthenticationResult authResult = null;
    IPublicClientApplication app =  PublicClientApplicationBuilder.Create(ClientId).WithRedirectUri($"msal{ClientId}://auth").Build();
    //var app = PublicClientApplicationBuilder.Create(ClientId).Build();
    IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
    try
    {
        //IAccount firstAccount = accounts.FirstOrDefault();
        authResult = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
        Debug.Log("Got Token from cache...");
    }
    catch (MsalUiRequiredException ex)
    {
        try
        {
            var actClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            var activity = actClass.GetStatic<AndroidJavaObject>("currentActivity");


                Debug.Log("Launching interactive login");

                authResult = await app.AcquireTokenInteractive(scopes).WithParentActivityOrWindow(activity)
                    .ExecuteAsync();

                Debug.Log("Returned from interactive login");


        }
        catch (Exception exx)
        {
            Debug.Log("exx: " + exx);
        }


    }
    if (authResult != null)
    {
        Debug.Log("username: " + authResult.Account.Username);
    }
}

}

Then build out to android, It hits errors as it tries to calltokenasync, as far s I can tell it wants an android activity there, but even with some fumbling around in java I don't seem to be able to paste one in.

@JolofNar : would you have a zip file that you could upload (using "Attach files by dragging & dropping" below a comment, and also a link on how to install the environment (unless this is in Visual Studio). As far as I'm concerned, Just to give you an idea; I did not know that Android Unity existed, and I would not even know how to create a scene (I guess I can look it up, but that's not knowledge I have today)

I will have to make up a project to share, as the one I'm currently working on is from work.

Here is the project file
Msal Test.zip

Did you ever get this working? I'm trying to get MSAL working in Unity as well, but I get an exception on AquireTokenInteractive

System.PlatformNotSupportedException: Possible Cause: If you are using an XForms app, or generally a netstandard assembly, make sure you add a reference to Microsoft.Identity.Client.dll from each platform assembly (e.g. UWP, Android, iOS)

I assume I have to include some aar or jar files...

Any help regarding this would really be appreciated

Hi HeinA

This was a while back so I鈥檓 not remembering perfectly. But. I think that we couldn鈥檛 get MSAL to work the way we wanted for the project. And ended up using OAUTH 2.0.

The issue as I recall was getting the key sent back to the app. And even OAUTH wasn鈥檛 super easy. There just isn鈥檛 anything specific to Unity that lets you do it without branching into android areas. Although I think it probably is possible (and waaaaay easier) if you know java to build and handle all this stuff in there. And then call the function on the java plugin from unity. And get a string returned.
Again I can鈥檛 remember exactly which solution we ended up going with (we had to come up with a bunch of wacky work arounds).
But we looked at opening a web browser in the app. Because then it鈥檚 easy to get the key back. But that won鈥檛 work for every platform.
We also used device code for a while, while which is pretty easy to get working but doesn鈥檛 have a nice user flow at all.
Lastly we had it calling a java plugin to authorise. But had issues getting the key back to the app, I think due to a lack of knowledge with android manifest files.

That is very sad to hear. Thank you for the reply

I cannot verify if this works and I expect it would be a bit complicated; however could try this is you have time:
https://docs.unity3d.com/Manual/AndroidAARPlugins.html

MSAL Android is an AAR... so you it looks like you could add it as a plugin... not sure how to invoke it from Unity: https://github.com/AzureAD/microsoft-authentication-library-for-android

Thanx. I'll look at that. I have included that library already to see if it fixes a run time error

@HeinA did you manage to get it to work? We are also trying to use MSAL in Unity for Android and iOS

For people reading this in future, it's possible to use msal for android in Unity.
1) Include msal as an aar plugin (or you can pull it as a gradle dependency using maintemplate.gradle support in Unity).
2) Make an android library that calls msal and returns the access token as string. Unity supports calling Android classes and returning values from them: https://docs.unity3d.com/ScriptReference/AndroidJavaClass.html, https://docs.unity3d.com/ScriptReference/AndroidJavaProxy.html

For initializing msal you need a reference to the Unity activity. On C# side get Unity activity like this:

AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject unityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

Pass this to java plugin and call getApplicationContext() to get the context needed to initialize msal.

@ashikns - wouldn't it be easier to use the native MSAL for Android - https://github.com/AzureAD/microsoft-authentication-library-for-android which is written in Java? Or does Unity still need a CLR entry-point?

@bgavrilMS What I wrote is for native msal android. Unity -> proxy class -> native msal is the data flow and reverse for getting access token back in Unity.

Hi everybody, I have been trying to implement MSAL for Android in Unity for over two weeks. But im stuck in creating the proxy class that calls MSAL. I have some questions that maybe @ashikns or someone else could answer me.

  1. Do i have to include MSAL as an aar plugin in my unity project or do i have to add it to my proxy class?
  2. When i call MSAL from my proxy class (which is an Android Library) it throws me errors of type "NoClassDefFoundError" of a lot of classes from MSAL. Am i not initializing MSAL the right way? Am i not adding MSAL to the right place? Am i not referencing it the right way?

My Proxy Class:

package com.iar.mfa;

import android.app.Activity;

import androidx.annotation.NonNull;

import com.microsoft.identity.client.HttpMethod;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication;
import com.microsoft.identity.client.IPublicClientApplication;
import com.microsoft.identity.client.ISingleAccountPublicClientApplication;
import com.microsoft.identity.client.Prompt;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.exception.MsalException;

public class Autenticator {
private MsalWrapper mMsalWrapper;
public String Token;

public String Autenticate(Activity activity) {
    Token = "";

    MsalWrapper.create(activity,
            Constants.getResourceIdFromConfigFile(Constants.ConfigFile.WEBVIEW),
            new INotifyOperationResultCallback<MsalWrapper>() {
                @Override
                public void onSuccess(MsalWrapper result) {
                    mMsalWrapper = result;
                }

                @Override
                public void showMessage(String message) {
                    //showMessageWithToast(message);
                }
            });

    INotifyOperationResultCallback acquireTokenCallback = new INotifyOperationResultCallback<IAuthenticationResult>() {
        @Override
        public void onSuccess(IAuthenticationResult result) {
            Token = result.getAccessToken();
        }

        @Override
        public void showMessage(String message) {
            //showMessageWithToast(message);
        }
    };

    mMsalWrapper.acquireToken(activity, getCurrentRequestOptions(),acquireTokenCallback);

    return Token;
}

public void callback(){

}
private RequestOptions getCurrentRequestOptions() {
    final Constants.ConfigFile configFile = Constants.ConfigFile.WEBVIEW;
    final String loginHint = "";
    final IAccount account = null;
    final Prompt promptBehavior = Prompt.LOGIN;
    final String scopes = "user.read";
    final String extraScopesToConsent = "";
    final String claims = "";
    final boolean enablePII = false;
    final boolean forceRefresh = false;
    final String authority = "";
    final Constants.AuthScheme authScheme = Constants.AuthScheme.BEARER;
    final String httpMethodTextFromSpinner = "NONE_NULL";
    final HttpMethod popHttpMethod = null;
    final String popResourceUrl = "";

    return new RequestOptions(
            configFile,
            loginHint,
            account,
            promptBehavior,
            scopes,
            extraScopesToConsent,
            claims,
            enablePII,
            forceRefresh,
            authority,
            authScheme,
            popHttpMethod,
            popResourceUrl
    );
}

}

My Build.Gradle class:

apply plugin: 'com.android.library'

android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
signingConfigs {
debug {
storeFile file("./debug.keystore")
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}

    release {
        storeFile file("./debug.keystore")
        storePassword 'android'
        keyAlias 'androiddebugkey'
        keyPassword 'android'
    }
}
buildTypes {
    release {
        signingConfig signingConfigs.release
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
lintOptions {
    abortOnError false
}
flavorDimensions "main"
productFlavors {
    local {
        //applicationIdSuffix ".local"
        versionNameSuffix "-local"
        resValue("string", "application_name", "msal-local")
    }
    dist {
        // Keep .local because the redirect url we registered on the portal contains .local, not .dist
        //applicationIdSuffix ".local"
        versionNameSuffix "-dist"
        resValue("string", "application_name", "msal-dist")
    }
}

}

dependencies {
// Compile Dependency
localImplementation project(':msal')
distImplementation 'com.microsoft.identity.client:msal:1.+'
implementation "com.google.code.gson:gson:$rootProject.ext.gsonVersion"
implementation "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
implementation "androidx.legacy:legacy-support-v4:$rootProject.ext.legacySupportV4Version"
implementation "com.google.android.material:material:$rootProject.ext.materialVersion"
}

My unity class:

public void SignNative() {

    //Llamamos a la actividad de android
    AndroidJavaClass UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    AndroidJavaObject context = UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
    AndroidJavaObject tz_object = new AndroidJavaObject("com.iar.mfa.Autenticator");

    string token = tz_object.Call<string>("Autenticate", context);

}

I would appreciate if someone could help me out.
Thank you in advance

You have to include MSAL as a gradle dependency to your aar library project, and also include msal as a dependency in the gradle file that Unity accepts as input. Also there are a couple samples on github that demonstrate different approaches to getting android native plugins working in Android - and really the issue you're facing here is that - how to do Unity + Android native. Not MSAL.

Closing as external per @ashikns's comment

Was this page helpful?
0 / 5 - 0 ratings