Вход в систему MSAL застревает в приложении .NET MAUIAndroid

Форум для тех, кто программирует под Android
Ответить Пред. темаСлед. тема
Anonymous
 Вход в систему MSAL застревает в приложении .NET MAUI

Сообщение Anonymous »

Я использую MSAL для аутентификации пользователей в Azure AD B2C в своем приложении .NET MAUI, ориентированном на .NET 9.
Все работало нормально, когда мое приложение ориентировалось на .NET 8, но после его обновления до .NET 9 вход пользователя зависает на Android - на iOS все работает нормально. Запускает браузер, но дальше никуда не идет. Страница входа не открывается.
Я вижу следующие ошибки/предупреждения в консоли:

MSAL: EventLogLevel : Предупреждение, сообщение: False MSAL 4.66.2.0
MSAL.Xamarin.Android .NET 9.0.0 32 [2025-01-08 07:15:15Z -
a4d1f3c3-5806-4c30-976f-e8a4dd617008] Браузер с пакетом пользовательских вкладок
не доступен. Запускаю через альтернативный браузер. Подробнее см.
https://aka.ms/msal-net-system-browsers.

Код MSAL во многом аналогичен Microsoft представлено в этом репозитории: https://github.com/Azure-Samples/ms-ide ... tcore-maui
Единственное изменение, которое я сделал, это то, что я добавил bool mustPromptLogin потому что я не хочу, чтобы мое приложение автоматически открывало браузер с формой входа. Я хочу, чтобы пользователи переходили на мой LoginPage.xaml и нажимали кнопку, чтобы начать процесс входа в систему. Весь остальной код должен соответствовать тому, что Microsoft предоставила в примере кода.
Кстати, я закомментировал .WithUseEmbeddedWebView(true) в MSALClientHelper.cs, но это проблема не решена.
Буду признателен за любые предложения по устранению этой проблемы. Спасибо!
Вот MSALClientHelper.cs:
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.IdentityModel.Abstractions;
using System.Diagnostics;

namespace MyApp.Utils.MSALClient
{
public class MSALClientHelper
{
///
/// As for the Tenant, you can use a name as obtained from the azure portal, e.g. kko365.onmicrosoft.com"
///
public AzureADB2CConfig AzureADB2CConfig;

///
/// Gets the authentication result (if available) from MSAL's various operations.
///
///
/// The authentication result.
///
public AuthenticationResult AuthResult { get; private set; }

///
/// Gets the MSAL public client application instance.
///
///
/// The public client application.
///
public IPublicClientApplication PublicClientApplication { get; private set; }

///
/// This will determine if the Interactive Authentication should be Embedded or System view
///
public bool UseEmbedded { get; set; } = false;

///
/// The PublicClientApplication builder used internally
///
private PublicClientApplicationBuilder PublicClientApplicationBuilder;

// Token Caching setup - Mac
public static readonly string KeyChainServiceName = "MyCompany.MyApp";

public static readonly string KeyChainAccountName = "MSALCache";

// Token Caching setup - Linux
public static readonly string LinuxKeyRingSchema = "com.mycompany.myapp.msaltokencache";

public static readonly string LinuxKeyRingCollection = MsalCacheHelper.LinuxKeyRingDefaultCollection;
public static readonly string LinuxKeyRingLabel = "MSAL token cache for MyApp.";
public static readonly KeyValuePair LinuxKeyRingAttr1 = new KeyValuePair("Version", "1");
public static readonly KeyValuePair LinuxKeyRingAttr2 = new KeyValuePair("ProductGroup", "Contoso");

private static string PCANotInitializedExceptionMessage = "The PublicClientApplication needs to be initialized before calling this method. Use InitializePublicClientAppAsync() to initialize.";

///
/// Initializes a new instance of the class.
///
public MSALClientHelper(AzureADB2CConfig azureADB2CConfig)
{
AzureADB2CConfig = azureADB2CConfig;

this.InitializePublicClientApplicationBuilder();
}

///
/// Initializes the MSAL's PublicClientApplication builder from config.
///
///
private void InitializePublicClientApplicationBuilder()
{
this.PublicClientApplicationBuilder = PublicClientApplicationBuilder.Create(AzureADB2CConfig.ClientId)
.WithExperimentalFeatures() // this is for upcoming logger
.WithB2CAuthority($"{AzureADB2CConfig.Instance}/tfp/{AzureADB2CConfig.Domain}/{AzureADB2CConfig.SignUpSignInPolicyid}")
.WithLogging(new IdentityLogger(EventLogLevel.Warning), enablePiiLogging: false) // This is the currently recommended way to log MSAL message. For more info refer to https://github.com/AzureAD/microsoft-au ... ki/logging. Set Identity Logging level to Warning which is a middle ground
.WithIosKeychainSecurityGroup("com.microsoft.adalcache");
}

///
/// Initializes the public client application of MSAL.NET with the required information to correctly authenticate the user.
///
///
public async Task InitializePublicClientAppAsync()
{
// Initialize the MSAL library by building a public client application
this.PublicClientApplication = this.PublicClientApplicationBuilder
.WithRedirectUri($"msal{PublicClientSingleton.Instance.MSALClientHelper.AzureADB2CConfig.ClientId}://auth")
#if ANDROID
.WithParentActivityOrWindow(() => Platform.CurrentActivity)
#endif
.Build();

await AttachTokenCache();
return await FetchSignedInUserFromCache().ConfigureAwait(false);
}

///
/// Attaches the token cache to the Public Client app.
///
/// IAccount list of already signed-in users (if available)
private async Task AttachTokenCache()
{
if (DeviceInfo.Current.Platform != DevicePlatform.WinUI)
{
return null;
}

// Cache configuration and hook-up to public application. Refer to https://github.com/AzureAD/microsoft-au ... oken-cache
var storageProperties = new StorageCreationPropertiesBuilder(AzureADB2CConfig.CacheFileName, AzureADB2CConfig.CacheDir)
.Build();

var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
msalcachehelper.RegisterCache(PublicClientApplication.UserTokenCache);

// If the cache file is being reused, we'd find some already-signed-in accounts
return await PublicClientApplication.GetAccountsAsync().ConfigureAwait(false);
}

///
/// Signs in the user and obtains an Access token for a provided set of scopes
///
///

/// Access Token
public async Task SignInUserAndAcquireAccessToken(string[] scopes, bool shouldPromptLogin)
{
Exception.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);

var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false);

try
{
// 1. Try to sign-in the previously signed-in account
if (existingUser != null)
{
this.AuthResult = await this.PublicClientApplication
.AcquireTokenSilent(scopes, existingUser)
.ExecuteAsync()
.ConfigureAwait(false);
}
else
{
if (shouldPromptLogin)
this.AuthResult = await SignInUserInteractivelyAsync(scopes);
else
return null;
}
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenInteractive to acquire a token interactively
Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");

if(shouldPromptLogin)
this.AuthResult = await this.PublicClientApplication
.AcquireTokenInteractive(scopes)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalException msalEx)
{
Debug.WriteLine($"Error Acquiring Token interactively:{Environment.NewLine}{msalEx}");
}

return this.AuthResult != null && !string.IsNullOrWhiteSpace(this.AuthResult.AccessToken) ? this.AuthResult.AccessToken : null;
}

///
/// Signs the in user and acquire access token for a provided set of scopes.
///
/// The scopes.
/// The extra claims, usually from CAE. We basically handle CAE by sending the user back to Azure AD for
/// additional processing and requesting a new access token for Graph
///
public async Task SignInUserAndAcquireAccessToken(string[] scopes, string extraclaims)
{
Exception.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);

try
{
// Send the user to Azure AD for re-authentication as a silent acquisition wont resolve any CAE scenarios like an extra claims request
this.AuthResult = await this.PublicClientApplication.AcquireTokenInteractive(scopes)
.WithClaims(extraclaims)
.ExecuteAsync()
.ConfigureAwait(false);
}
catch (MsalException msalEx)
{
Debug.WriteLine($"Error Acquiring Token:{Environment.NewLine}{msalEx}");
}

return this.AuthResult.AccessToken;
}

///
/// Shows a pattern to sign-in a user interactively in applications that are input constrained and would need to fall-back on device code flow.
///
/// The scopes.
/// The existing account.
///
public async Task SignInUserInteractivelyAsync(string[] scopes, IAccount existingAccount = null)
{

Exception.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);

if (this.PublicClientApplication == null)
throw new NullReferenceException();

if (this.PublicClientApplication.IsUserInteractive())
{
#if IOS
SystemWebViewOptions systemWebViewOptions = new SystemWebViewOptions();
#endif

return await this.PublicClientApplication.AcquireTokenInteractive(scopes)
.WithUseEmbeddedWebView(true)
.WithParentActivityOrWindow(PlatformConfig.Instance.ParentWindow)
.ExecuteAsync()
.ConfigureAwait(false);
#if IOS
// Hide the privacy prompt in iOS
systemWebViewOptions.iOSHidePrivacyPrompt = true;
#endif
}

// If the operating system does not have UI (e.g. SSH into Linux), you can fallback to device code, however this
// flow will not satisfy the "device is managed" CA policy.
return await this.PublicClientApplication.AcquireTokenWithDeviceCode(scopes, (dcr) =>
{
Console.WriteLine(dcr.Message);
return Task.CompletedTask;
}).ExecuteAsync().ConfigureAwait(false);
}

///
/// Removes the first signed-in user's record from token cache
///
public async Task SignOutUserAsync()
{
var existingUser = await FetchSignedInUserFromCache().ConfigureAwait(false);
await this.SignOutUserAsync(existingUser).ConfigureAwait(false);
}

///
/// Removes a given user's record from token cache
///
/// The user.
public async Task SignOutUserAsync(IAccount user)
{
if (this.PublicClientApplication == null) return;

await this.PublicClientApplication.RemoveAsync(user).ConfigureAwait(false);
}

///
/// Fetches the signed in user from MSAL's token cache (if available).
///
///
public async Task FetchSignedInUserFromCache()
{
Exception.ThrowOn(() => this.PublicClientApplication == null, PCANotInitializedExceptionMessage);

// get accounts from cache
IEnumerable accounts = await this.PublicClientApplication.GetAccountsAsync();

// Error corner case: we should always have 0 or 1 accounts, not expecting > 1
// This is just an example of how to resolve this ambiguity, which can arise if more apps share a token cache.
// Note that some apps prefer to use a random account from the cache.
if (accounts.Count() > 1)
{
foreach (var acc in accounts)
{
await this.PublicClientApplication.RemoveAsync(acc);
}

return null;
}

return accounts.SingleOrDefault();
}
}
}

А вот PublicClientSingleton.cs:
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace MyApp.Utils.MSALClient
{
public class PublicClientSingleton
{
///
/// This is the configuration for the application found within the 'appsettings.json' file.
///
IConfiguration AppConfiguration;

///
/// This is the singleton used by Ux. Since PublicClientWrapper constructor does not have perf or memory issue, it is instantiated directly.
///
public static PublicClientSingleton Instance { get; private set; } = new PublicClientSingleton();

///
/// Gets the instance of MSALClientHelper.
///
public DownstreamApiHelper DownstreamApiHelper { get; }

///
/// Gets the instance of MSALClientHelper.
///
public MSALClientHelper MSALClientHelper { get; }

///
/// This will determine if the Interactive Authentication should be Embedded or System view
///
public bool UseEmbedded { get; set; } = false;

//// Custom logger for sample
//private readonly IdentityLogger _logger = new IdentityLogger();

///
/// Prevents a default instance of the class from being created. or a private constructor for singleton
///
[MethodImpl(MethodImplOptions.NoInlining)]
private PublicClientSingleton()
{
// Load config
// https://stackoverflow.com/questions/702 ... e-on-andro
var assembly = Assembly.GetExecutingAssembly();
string embeddedConfigfilename = $"{Assembly.GetCallingAssembly().GetName().Name}.appsettings.json";
using var stream = assembly.GetManifestResourceStream(embeddedConfigfilename);
AppConfiguration = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();

AzureADB2CConfig azureADConfig = AppConfiguration.GetSection("AzureAdB2C").Get();
this.MSALClientHelper = new MSALClientHelper(azureADConfig);

DownStreamApiConfig downStreamApiConfig = AppConfiguration.GetSection("DownstreamApi").Get();
this.DownstreamApiHelper = new DownstreamApiHelper(downStreamApiConfig, this.MSALClientHelper);
}

///
/// Acquire the token silently
///
/// An access token
public async Task AcquireTokenSilentAsync(bool shouldPromptLogin)
{
// Get accounts by policy
return await this.AcquireTokenSilentAsync(this.GetScopes(), shouldPromptLogin).ConfigureAwait(false);
}

///
/// Acquire the token silently
///
///
desired scopes
/// An access token
public async Task AcquireTokenSilentAsync(string[] scopes, bool shouldPromptLogin)
{
var output = await this.MSALClientHelper.SignInUserAndAcquireAccessToken(scopes, shouldPromptLogin).ConfigureAwait(false);
return output;
}

///
/// Perform the interactive acquisition of the token for the given scope
///
/// desired scopes
///
internal async Task AcquireTokenInteractiveAsync(string[] scopes)
{
this.MSALClientHelper.UseEmbedded = this.UseEmbedded;
return await this.MSALClientHelper.SignInUserInteractivelyAsync(scopes).ConfigureAwait(false);
}

///
/// It will sign out the user.
///
///
internal async Task SignOutAsync()
{
await this.MSALClientHelper.SignOutUserAsync().ConfigureAwait(false);
}

///
/// Gets scopes for the application
///
/// An array of all scopes
internal string[] GetScopes()
{
return this.DownstreamApiHelper.DownstreamApiConfig.ScopesArray;
}
}
}

А вот MainActivity.cs:
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density, ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : MauiAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// configure platform specific params
PlatformConfig.Instance.RedirectUri = $"msal{PublicClientSingleton.Instance.MSALClientHelper.AzureADB2CConfig.ClientId}://auth";
PlatformConfig.Instance.ParentWindow = this;

// Initialize MSAL and platformConfig is set
_ = Task.Run(async () => await PublicClientSingleton.Instance.MSALClientHelper.InitializePublicClientAppAsync()).Result;
}

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
}
}

И, наконец, вот AndroidManifest.xml:









































Подробнее здесь: https://stackoverflow.com/questions/793 ... t-maui-app
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Вход в систему MSAL застревает в приложении .NET MAUI
    Anonymous » » в форуме Android
    0 Ответы
    18 Просмотры
    Последнее сообщение Anonymous
  • Получите учетные записи, в которые уже выполнен вход в MSAL (MAUI)
    Anonymous » » в форуме C#
    0 Ответы
    31 Просмотры
    Последнее сообщение Anonymous
  • MSAL.NET вход не совсем подписывается
    Anonymous » » в форуме C#
    0 Ответы
    2 Просмотры
    Последнее сообщение Anonymous
  • MSAL.NET вход не совсем подписывается
    Anonymous » » в форуме C#
    0 Ответы
    4 Просмотры
    Последнее сообщение Anonymous
  • Azure AD B2C с использованием MSAL в приложении платформы .NET для надстроек Excel
    Anonymous » » в форуме C#
    0 Ответы
    23 Просмотры
    Последнее сообщение Anonymous

Вернуться в «Android»