Skip to content

Microsoft.Identity.Client.MsalClientException: User canceled authentication: .Net Maui #5772

@Nandulucky

Description

@Nandulucky

Library version used

4.77.0

.NET version

9.0.308

Scenario

PublicClient - mobile app

Is this a new or an existing app?

The app is in production, and I have upgraded to a new version of MSAL

Issue description and reproduction steps

Library version : 4.77.0
<PackageReference Include="Microsoft.Identity.Client" Version="4.77.0" /> <PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.77.0" />
.Net Version 9.0.308

Exception:
Exception: MSAL.Xamarin.Android.4.77.0.0.MsalClientException: ErrorCode: authentication_canceled Microsoft.Identity.Client.MsalClientException: User canceled authentication. On an Android device, this could be due to the lack of capabilities, such as custom tabs, for the system browser. See https://aka.ms/msal-net-system-browsers for more information.

StackTrace:
at Microsoft.Identity.Client.Internal.AuthCodeRequestComponent.VerifyAuthorizationResult(AuthorizationResult authorizationResult, String originalState)
at Microsoft.Identity.Client.Internal.AuthCodeRequestComponent.FetchAuthCodeAndPkceInternalAsync(IWebUI webUi, CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.AuthCodeRequestComponent.FetchAuthCodeAndPkceVerifierAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.GetTokenResponseAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest.ExecuteAsync(CancellationToken cancellationToken)
at Microsoft.Identity.Client.Internal.Requests.RequestBase.<>c__DisplayClass11_1.<b__1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.Identity.Client.Utils.StopwatchService.MeasureCodeBlockAsync(Func1 codeBlock) at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken) at Microsoft.Identity.Client.ApiConfig.Executors.PublicClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenInteractiveParameters interactiveParameters, CancellationToken cancellationToken) at Jacobs.Services.MSAuthService.PerformInteractiveAuthenticationAsync(String[] scopes) at Jacobs.Services.MSAuthService.<>c__DisplayClass5_01.<b__0>d[[Microsoft.Identity.Client.AuthenticationResult, Microsoft.Identity.Client, Version=4.77.0.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae]].MoveNext()

Relevant code snippets

`public async Task<bool> GetAccessToken()
        {
            // Gets the available accounts
            var accounts = await _pca.GetAccountsAsync();

            // Stores first account available
            Common.Account = accounts.FirstOrDefault();

            if (Common.Account != null)
            {
                try
                {
                    // This call acquires the access token silently without asking for user credentials
                    var authResult = await _pca.AcquireTokenSilent(Configurations.Scopes, Common.Account).ExecuteAsync();

                    // Stores the access token
                    AppConstants.AccessToken = authResult.AccessToken;

                    return true;
                }
                catch (MsalUiRequiredException ex)
                {
                    ///add exception logging
                }
            }
            // Interactive auth needed - ensure we're on UI thread for the entire operation
            var interactiveResult = await InvokeOnMainThreadAsync(() =>
                PerformInteractiveAuthenticationAsync(Configurations.Scopes));

            if (interactiveResult != null)
            {
                AppConstants.AccessToken = interactiveResult.AccessToken;

                await GetHttpContentWithTokenAsync(interactiveResult.AccessToken);

                // Handle iOS cookie capture
                if (DeviceInfo.Platform == DevicePlatform.iOS)
                {
                    AppConstants.OauthCookie = _cookieStore.CurrentCookies
                        .FirstOrDefault(cc => cc.Name == "stsservicecookie");
                }

                Common.Account = interactiveResult.Account;
                return true;
            }

            return false;
        }
        // Helper to ensure code runs on UI thread and properly awaits completion
        private async Task<T> InvokeOnMainThreadAsync<T>(Func<Task<T>> action)
        {
            TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();

            MainThread.BeginInvokeOnMainThread(async () => {
                try
                {
                    var result = await action();
                    tcs.SetResult(result);
                }
                catch (Exception ex)
                {
                    if (Common.MSAuthService != null && Common.DataService != null)
                    {
                       await Common.DataService.LogToAzureStorage(Common.MSAuthService, Common.LogException("InvokeOnMainThreadAsync", ex.Message, ex.StackTrace));
                    }
                    CrashlyticsLogger.LogException("InvokeOnMainThreadAsync", ex);
                    tcs.SetException(ex);
                }
            });

            return await tcs.Task;
        }

        private async Task<AuthenticationResult> PerformInteractiveAuthenticationAsync(string[] scopes)
        {
            try
            {
                // Get appropriate parent for current platform
                object parent = null;
                if (DeviceInfo.Platform == DevicePlatform.Android)
                {
#if ANDROID
                    parent = Platform.CurrentActivity;
#endif
                }
                else if (DeviceInfo.Platform == DevicePlatform.iOS)
                {
#if IOS
                    parent = UIKit.UIApplication.SharedApplication.KeyWindow?.RootViewController;
#endif
                }

                // Configure and execute the interactive authentication
                var builder = _pca.AcquireTokenInteractive(scopes)
                    .WithParentActivityOrWindow(parent)
                    .WithUseEmbeddedWebView(true);

                // Important: We're already on the UI thread, so no ConfigureAwait(false) here
                return await builder.ExecuteAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Interactive auth error: {ex.GetType().Name} - {ex.Message}");
                throw;
            }
        }
`

Expected behavior

30% of users facing this issue
Please help me out this one

Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

Regression

4.77.0

Solution and workarounds

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions