AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET
https://aka.ms/msal-net
MIT License
1.39k stars 340 forks source link

Msal Unity integration(Android) #1152

Closed JolofNar closed 3 years ago

JolofNar commented 5 years ago

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.

jmprieur commented 5 years ago

@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 ...

JolofNar commented 5 years ago

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.

jmprieur commented 5 years ago

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

JolofNar commented 5 years ago

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

JolofNar commented 5 years ago

Here is the project file Msal Test.zip

HeinA commented 5 years ago

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

JolofNar commented 5 years ago

Hi HeinA

This was a while back so I’m not remembering perfectly. But. I think that we couldn’t 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’t super easy. There just isn’t 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’t 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’s easy to get the key back. But that won’t work for every platform.
We also used device code for a while, while which is pretty easy to get working but doesn’t 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.

HeinA commented 5 years ago

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

shoatman commented 5 years ago

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

HeinA commented 5 years ago

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

panjevic commented 4 years ago

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

ashikns commented 4 years ago

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.

bgavrilMS commented 4 years ago

@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?

ashikns commented 4 years ago

@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.

Soulzr commented 4 years ago

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

ashikns commented 4 years ago

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.

jmprieur commented 3 years ago

Closing as external per @ashikns's comment

timothyt commented 1 year ago

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.

Thanks this was helpful and we've successfully gotten this working in our Unity app

JanaSivaGithub commented 11 months ago

Hi, we are trying to implement MSAL in Unity. Have installed MSAL with Nuget in Unity application. Have configured redirect URL to "http"//localhost" and hae written the code. It's working in PlayMode (Able to authenticate the user and receiving the access token). But the problem is it's throwing error in apk run, getting below error: MSALBrowserIssue

It's not able to open a browser with commands like xdg-open and related

Any help will be much appreciated.