Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Interactive flow not working in Unity 2021 #2766

Closed
1 of 7 tasks
eusebiu opened this issue Jul 14, 2021 · 10 comments
Closed
1 of 7 tasks

[Bug] Interactive flow not working in Unity 2021 #2766

eusebiu opened this issue Jul 14, 2021 · 10 comments

Comments

@eusebiu
Copy link

eusebiu commented Jul 14, 2021

Logs and network traces
None

Which version of MSAL.NET are you using?
4.34 - the Microsoft.Identity.Client.dll is added as a Unity plugin (for Editor and Standalone only)

Platform
.NET 4.5, Unity 2021

What authentication flow has the issue?

  • Desktop / Mobile
    • Interactive
    • Integrated Windows Authentication
    • Username Password
    • Device code flow (browserless)
  • Web app
    • Authorization code
    • On-Behalf-Of
  • Daemon app
    • Service to Service calls

Is this a new or existing app?
This is a new app or experiment.

Repro

            string[] scopes = new string[] { "user.read" };

            var app = PublicClientApplicationBuilder
                         .Create(Preferences.ClientId)
                         .WithTenantId(Preferences.TenantId)
                         .WithLogging(MyLoggingMethod, LogLevel.Info,
                           enablePiiLogging: true,
                           enableDefaultPlatformLogging: true)
                         .Build();

            var accounts = await app.GetAccountsAsync();

            AuthenticationResult result = null;
            if (accounts.Any())
            {
                result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                                    .ExecuteAsync();
            }
            else
            {
                try
                {
                    result = await app.AcquireTokenInteractive(scopes)
                                        .ExecuteAsync(CancellationToken.None);
                }
                catch (MsalUiRequiredException ex)
                { }
            }

Expected behavior
The login popup appears or account is detected.

Actual behavior
Errors appear in the log - nothing happens.

NotImplementedException: The method or operation is not implemented.
System.Runtime.InteropServices.Marshal.ReadInt16 (System.Object ptr, System.Int32 ofs) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Drawing.Font.FromLogFont (System.Object lf, System.IntPtr hdc) (at <1e9166bfca654377b09cea42d003812d>:0)
System.Drawing.Font.FromHfont (System.IntPtr hfont) (at <1e9166bfca654377b09cea42d003812d>:0)
System.Drawing.SystemFonts.get_DefaultFont () (at <1e9166bfca654377b09cea42d003812d>:0)
System.Windows.Forms.Control.get_DefaultFont () (at <3b28f5500d544e019e30dca09fab36d7>:0)
System.Windows.Forms.Control.get_Font () (at <3b28f5500d544e019e30dca09fab36d7>:0)
System.Windows.Forms.WebBrowserBase.get_Font () (at <3b28f5500d544e019e30dca09fab36d7>:0)
System.Windows.Forms.Control.AssignParent (System.Windows.Forms.Control value) (at <3b28f5500d544e019e30dca09fab36d7>:0)
System.Windows.Forms.Control+ControlCollection.Add (System.Windows.Forms.Control value) (at <3b28f5500d544e019e30dca09fab36d7>:0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialogBase.b__32_0 () (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialogBase.InvokeHandlingOwnerWindow (System.Action action) (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialogBase.InitializeComponent () (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialogBase..ctor (System.Object ownerWindow) (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialog..ctor (System.Object ownerWindow) (at :0)
(wrapper remoting-invoke-with-check) Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WindowsFormsWebAuthenticationDialog..ctor(object)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.InteractiveWebUI.OnAuthenticate () (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WebUI.Authenticate (System.Uri requestUri, System.Uri callbackUri) (at :0)
Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WebUI+<>c__DisplayClass20_0.b__1 (System.Object tcs) (at :0)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0) Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi.WebUI+<AcquireAuthorizationAsync>d__20.MoveNext () (at <f4fac412be4346e4bdfeff1f81897b10>:0) --- End of stack trace from previous location where exception was thrown --- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
Microsoft.Identity.Client.Internal.AuthCodeRequestComponent+d__7.MoveNext () (at :0)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0) Microsoft.Identity.Client.Internal.AuthCodeRequestComponent+<FetchAuthCodeAndPkceVerifierAsync>d__4.MoveNext () (at <f4fac412be4346e4bdfeff1f81897b10>:0) --- End of stack trace from previous location where exception was thrown --- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
Microsoft.Identity.Client.Internal.Requests.InteractiveRequest+d__11.MoveNext () (at :0)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0) Microsoft.Identity.Client.Internal.Requests.InteractiveRequest+<ExecuteAsync>d__8.MoveNext () (at <f4fac412be4346e4bdfeff1f81897b10>:0) --- End of stack trace from previous location where exception was thrown --- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
Microsoft.Identity.Client.Internal.Requests.RequestBase+d__12.MoveNext () (at :0)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0) Microsoft.Identity.Client.ApiConfig.Executors.PublicClientExecutor+<ExecuteAsync>d__2.MoveNext () (at <f4fac412be4346e4bdfeff1f81897b10>:0) --- End of stack trace from previous location where exception was thrown --- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0) System.Runtime.CompilerServices.TaskAwaiter1[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
UCB.XR.Virtu.Services.Security.AuthenticationService+d__0.MoveNext () (at Assets/Scripts/UCB/XR/Services/Security/AuthenticationService.cs:41)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
UCB.XR.Virtu.Security.AuthenticationManager+d__0.MoveNext () (at Assets/Scripts/XR/Security/AuthenticationManager.cs:11)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.b__6_0 (System.Object state) (at <695d1cc93cca45069c528c15c9fdd749>:0)
UnityEngine.UnitySynchronizationContext+WorkRequest.Invoke () (at :0)
UnityEngine.UnitySynchronizationContext:ExecuteTasks()

@bgavrilMS
Copy link
Member

bgavrilMS commented Jul 14, 2021

Unity doesn't use the .NET Framework, it uses Mono. AcquireTokenInteractive uses a WinForms based browser, which Mono does not support.

As far as I know, device code flow works reliably with Mono.

A better user experience would be with the system browser, which I think also works with Unity:

  1. Register http://localhost as redirect uri
  2. Add .WithEmbeddedWebview(false) to the AcquireTokenInteractive builder

@bgavrilMS
Copy link
Member

Also see #2305, of which this is a duplicate.

@eusebiu
Copy link
Author

eusebiu commented Jul 14, 2021

Indeed, Unity uses Mono but Windows Forms APIs can be invoked from Unity apps (using the dll from .Net Framework) - Windows only.
Also, your workaround seem to work. Thanks!

@eusebiu eusebiu closed this as completed Jul 14, 2021
@pmaytak pmaytak added the Unity label Jul 15, 2021
@visose
Copy link

visose commented Oct 30, 2021

@eusebiu could you clarify what is the workaround that worked for you?

@eusebiu
Copy link
Author

eusebiu commented Oct 30, 2021

@visose
This is what I did (Preferences is just a static class with the properties).

Public clients:

// after setting up the app in Azure AppRegistration
// RedirectUri=http://localhost
// ResourceScopes=user.read,https://<some_url>/api/user_impersonation
        public static async Task<AuthenticationResult> AuthenticateClientAsync()
        {
            if (string.IsNullOrEmpty(Preferences.ClientId) || string.IsNullOrEmpty(Preferences.TenantId) ||
                string.IsNullOrEmpty(Preferences.ResourceScopes) || string.IsNullOrEmpty(Preferences.RedirectUri))
                return null;

            string[] scopes = Preferences.ResourceScopes.Split(new[] { ',', ';' }, System.StringSplitOptions.RemoveEmptyEntries);

            var app = PublicClientApplicationBuilder
                         .Create(Preferences.ClientId)
                         .WithTenantId(Preferences.TenantId)
                         .WithRedirectUri(Preferences.RedirectUri)
                         .Build();

            var accounts = await app.GetAccountsAsync();

            AuthenticationResult result = null;
            if (accounts.Any())
            {
                result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                                    .ExecuteAsync();
            }
            else
            {
                try
                {
                    result = await app.AcquireTokenInteractive(scopes)
                                        .WithUseEmbeddedWebView(false)
                                        .ExecuteAsync(CancellationToken.None);
                }
                catch (MsalUiRequiredException ex)
                {
                    Debug.LogException(ex);
                    // MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application 
                    // with ID '{appId}' named '{appName}'.Send an interactive authorization request for this user and resource.

                    // you need to get user consent first. This can be done, if you are not using .NET Core (which does not have any Web UI)
                    // by doing (once only) an AcquireToken interactive.

                    // If you are using .NET core or don't want to do an AcquireTokenInteractive, you might want to suggest the user to navigate
                    // to a URL to consent: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read

                    // AADSTS50079: The user is required to use multi-factor authentication.
                    // There is no mitigation - if MFA is configured for your tenant and AAD decides to enforce it, 
                    // you need to fallback to an interactive flows such as AcquireTokenInteractive or AcquireTokenByDeviceCode
                }
                catch (MsalServiceException ex)
                {
                    Debug.LogException(ex);
                    // Kind of errors you could have (in ex.Message)

                    // MsalServiceException: AADSTS90010: The grant type is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint.
                    // you used common.
                    // Mitigation: as explained in the message from Azure AD, the authoriy needs to be tenanted or otherwise organizations

                    // MsalServiceException: AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.
                    // Explanation: this can happen if your application was not registered as a public client application in Azure AD 
                    // Mitigation: in the Azure portal, edit the manifest for your application and set the `allowPublicClient` to `true` 
                }
                catch (MsalClientException ex)
                {
                    Debug.LogException(ex);
                    // Error Code: unknown_user Message: Could not identify logged in user
                    // Explanation: the library was unable to query the current Windows logged-in user or this user is not AD or AAD 
                    // joined (work-place joined users are not supported). 

                    // Mitigation 1: on UWP, check that the application has the following capabilities: Enterprise Authentication, 
                    // Private Networks (Client and Server), User Account Information

                    // Mitigation 2: Implement your own logic to fetch the username (e.g. [email protected]) and use the 
                    // AcquireTokenByIntegratedWindowsAuth form that takes in the username

                    // Error Code: integrated_windows_auth_not_supported_managed_user
                    // Explanation: This method relies on an a protocol exposed by Active Directory (AD). If a user was created in Azure 
                    // Active Directory without AD backing ("managed" user), this method will fail. Users created in AD and backed by 
                    // AAD ("federated" users) can benefit from this non-interactive method of authentication.
                    // Mitigation: Use interactive authentication
                }
            }

            return result;
        }

If you need to authenticate using confidential client (for daemon apps - https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-acquire-token?tabs=dotnet):

        public static async Task<AuthenticationResult> AuthenticateServerAsync()
        {
            if (string.IsNullOrEmpty(Preferences.ApiClientId) || string.IsNullOrEmpty(Preferences.TenantId) || string.IsNullOrEmpty(Preferences.ApiClientSecret))
                return null;

            string[] scopes = new string[] { $"{Preferences.ApiServiceUrl}/.default" };

            var app = ConfidentialClientApplicationBuilder
                         .Create(Preferences.ApiClientId)
                         .WithAuthority(new System.Uri($"https://login.microsoftonline.com/{Preferences.TenantId}"))
                         .WithClientSecret(Preferences.ApiClientSecret)
                         .Build();

            AuthenticationResult result = null;
            try
            {
                result = await app.AcquireTokenForClient(scopes)
                                 .ExecuteAsync();
            }
            catch (MsalUiRequiredException ex)
            {
                Debug.LogException(ex);
                // MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application 
                // with ID '{appId}' named '{appName}'.Send an interactive authorization request for this user and resource.

                // you need to get user consent first. This can be done, if you are not using .NET Core (which does not have any Web UI)
                // by doing (once only) an AcquireToken interactive.

                // If you are using .NET core or don't want to do an AcquireTokenInteractive, you might want to suggest the user to navigate
                // to a URL to consent: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read

                // AADSTS50079: The user is required to use multi-factor authentication.
                // There is no mitigation - if MFA is configured for your tenant and AAD decides to enforce it, 
                // you need to fallback to an interactive flows such as AcquireTokenInteractive or AcquireTokenByDeviceCode
            }
            catch (MsalServiceException ex)
            {
                Debug.LogException(ex);
                // Kind of errors you could have (in ex.Message)

                // MsalServiceException: AADSTS90010: The grant type is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint.
                // you used common.
                // Mitigation: as explained in the message from Azure AD, the authoriy needs to be tenanted or otherwise organizations

                // MsalServiceException: AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.
                // Explanation: this can happen if your application was not registered as a public client application in Azure AD 
                // Mitigation: in the Azure portal, edit the manifest for your application and set the `allowPublicClient` to `true` 
            }
            catch (MsalClientException ex)
            {
                Debug.LogException(ex);
                // Error Code: unknown_user Message: Could not identify logged in user
                // Explanation: the library was unable to query the current Windows logged-in user or this user is not AD or AAD 
                // joined (work-place joined users are not supported). 

                // Mitigation 1: on UWP, check that the application has the following capabilities: Enterprise Authentication, 
                // Private Networks (Client and Server), User Account Information

                // Mitigation 2: Implement your own logic to fetch the username (e.g. [email protected]) and use the 
                // AcquireTokenByIntegratedWindowsAuth form that takes in the username

                // Error Code: integrated_windows_auth_not_supported_managed_user
                // Explanation: This method relies on an a protocol exposed by Active Directory (AD). If a user was created in Azure 
                // Active Directory without AD backing ("managed" user), this method will fail. Users created in AD and backed by 
                // AAD ("federated" users) can benefit from this non-interactive method of authentication.
                // Mitigation: Use interactive authentication
            }

            return result;
        }

@visose
Copy link

visose commented Oct 30, 2021

Thanks for sharing your code, I wasn't sure if by workaround you meant using device flow or adding .WithUseEmbeddedWebView(false). I tried the latter in Unity for a Windows desktop app and didn't manage to get it t work (only the device flow). I'll double check now with your code (on the surface it looks very similar, but I probably missed something).

@juanworkobot
Copy link

Hi @eusebiu,

I've managed to authenticate the user and then I get a message in the browser "Authentication complete. You can return to the application. Feel free to close this browser tab."

Is there a way to automatically go back to unity or to prompt an alert to go back to the app? Maybe with a custom url protocol registry?

Something like this. Please advise, I'm stuck.
image

@bgavrilMS
Copy link
Member

@juanworkobot - you can add a custom message or a custom redirect instead of the default Authentication complete. You can return to the application. Feel free to close this browser tab."

See https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.acquiretokeninteractiveparameterbuilder.withsystemwebviewoptions?view=azure-dotnet

@bgavrilMS
Copy link
Member

but you can't switch from browser back to app automatically.

@juanworkobot
Copy link

juanworkobot commented Jan 5, 2023

@bgavrilMS Thanks for the tip, worked great.
Sharing my findings here, might help someone else.
These works for a standalone windows build using Unity 2019.4.

  • Import Microsoft.Identity.Client and Microsoft.IdentityModel.Abstractions dlls to Assets/Plugins/x86_64. Kept getting errors without the Abstractions library in Unity 2019.4
  • Use WithTenantId if you want to restrict access only to the tenant.
  • WithRedirectUri and the Redirect URI in the app registration should be the same ("http://localhost")
  • Use SystemWebViewOptions for custom redirect after flow complete.
  • Used ContinueWith task method. Other methods like async/await froze the unity window.
using Microsoft.Identity.Client;

void AthenticateWithInteractiveFlow()
{
        try
        {
            var app = PublicClientApplicationBuilder.Create("yourClientId").WithTenantId("yourClientTenant").WithRedirectUri("http://localhost").Build();
            string[] scopes = new string[] { "user.read" };
            
            var options = new SystemWebViewOptions()
            {
                HtmlMessageSuccess = "<p> Your custom success message here: {0}. Details {1}</p>",
                HtmlMessageError = "<p> Your custom error message here: {0}. Details {1}</p>",
                BrowserRedirectSuccess = new Uri("https://www.yourdomainhere.com"),
                //BrowserRedirectSuccess = new Uri("customURLProtocol://"), // How to create custom URL protocol in windows: https://stackoverflow.com/questions/80650/how-do-i-register-a-custom-url-protocol-in-windows
            };
            app.AcquireTokenInteractive(scopes).WithUseEmbeddedWebView(false).WithSystemWebViewOptions(options).ExecuteAsync().ContinueWith(myTask =>
            {
                AuthenticationResult result = myTask.Result;
                string accessToken = result.AccessToken;
                Debug.Log($"{accessToken}");
            });
        }
        catch (MsalException error)
        {
            Debug.Log(error.Message);
        }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants