Аутентификация Azure AD B2C сбрасывается при переходе на страницу на основе MVVM

Вопрос из двух частей: при запуске My Xamarin.Forms app.cs переходит на страницу входа (ContentPage) с помощью кнопки. Я нажимаю кнопку и успешно вхожу в систему с этим обработчиком событий в коде приложения:

async void OnLoginButtonClicked(object sender, EventArgs e)
    {
        try
        {
            bool authenticated = await App.AuthenticationProvider.LoginAsync();
            if (authenticated)
            {
                Application.Current.MainPage = new PapMobLandingPage();
            }
            else
            {
                await DisplayAlert("Authentication", "Authentication", "OK");
            }
        }
        catch (MsalException ex)
        {
            if (ex.ErrorCode == "authentication_canceled")
            {
                await DisplayAlert("Authentication", "Authentication was cancelled by the user.", "OK");
            }
            else
            {
                await DisplayAlert("An error has occurred", "Exception message: " + ex.Message, "OK");
            }
        }
        catch (Exception ex)
        {
            await DisplayAlert("Authentication", "Authentication failed in a big way. Exception: " + ex.Message, "OK");
        }
    }
}

Затем меня перенаправляют на страницу 2 (PapMobLandingPage), на которой также есть кнопка. Я нажимаю эту кнопку PapMobLandingPage и меня перенаправляют на TabbedPage с информацией о вошедшем в систему пользователе, летящей прямо туда из моей базы данных Azure SQL, никаких проблем. Все отлично до сих пор! Вот обработчик события:

public async void GoToTabbedPage(object sender, EventArgs args)
        {
            await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new BotInTabbedPage());
        }

Вопрос 1: В коде BotInTabbedPage (TabbedPage) нет OnAppearing(), который проверяет, вошел ли пользователь в систему... при инициализации TabbedPage не происходит получение токенов, так как приложение узнает, что я m залогинился на этой странице??

Я посмотрел на аналогичный вопрос от @Creepin и, используя ссылку, я не мог понять ответ на его первоначальный вопрос о том, нужно ли вам аутентифицировать каждую страницу отдельно. Ссылка здесь:

использование AcquireTokenSilentAsync

Причина, по которой я задаю вопрос 1, заключается в том, что приведенная выше страница с вкладками является одним из шести вариантов, которые пользователь получает при входе в систему, поэтому мне понадобилась страница панели мониторинга (на основе MVVM) с шестью плитками. Когда я вставляю это между страницей 2 (PapMobLandingPage) и BotInTabbedPage (TabbedPage) и нажимаю на плитку TabbedPage, команда tap, связанная с плиткой, переводит меня на BotInTabbedPage (TabbedPage)... НО...

НЕТ ДАННЫХ КЛИЕНТА! МЕНЯ ВЫШЛИ!

Итак, подведем итог:

Страница входа -> PapMobLandingPage -> BotInTabbedPage = Остается аутентифицированным. Страница входа -> PapMobLandingPage -> Панель управления -> BotInTabbedPage = Отбрасывает аутентификацию.

Если я использую «ванильный» ContentPage с кнопкой:

Страница входа -> PapMobLandingPage -> ContentPage -> BotInTabbedPage = Остается аутентифицированным!

Итак, второй вопрос: кто-нибудь знает, почему?

Когда я говорю, что Dashboard основан на MVVM, я имею в виду, что у него есть XAML ContentPage, код .cs позади, с привязками значений к модели представления, которая также имеет шаблон модели представления и базу шаблонов. Это код:

XAML панели мониторинга:

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:local="clr-namespace:PapWine;assembly=PapWine" 
             xmlns:artina="clr-namespace:UXDivers.Artina.Shared;assembly=UXDivers.Artina.Shared"
             x:Class="PapWine.DashboardTaskMultipleTilesPage"
             BackgroundColor="Black"
             Title="{ artina:Translate PageTitleDashboardTaskMultipleTiles }">

    <ContentPage.Resources>
        <ResourceDictionary>
            <artina:BoolMemberTemplateSelector
                x:Key="Selector"
                MemberName="IsNotification">

                <artina:BoolMemberTemplateSelector.TrueDataTemplate>
                    <DataTemplate>
                        <local:DashboardAppNotificationItemTemplate
                            WidthRequest="145"
                            HeightRequest="145" />
                    </DataTemplate>
                </artina:BoolMemberTemplateSelector.TrueDataTemplate>

                <artina:BoolMemberTemplateSelector.FalseDataTemplate>
                    <DataTemplate>
                        <local:TaskTilesItemTemplate
                            ShowBackgroundImage="true"
                            ShowBackgroundColor="true"
                            ShowiconColoredCircleBackground="false"
                            TextColor="{ DynamicResource DashboardIconColor }"
                            WidthRequest="145"
                            HeightRequest="145"
                            />
                    </DataTemplate>
                </artina:BoolMemberTemplateSelector.FalseDataTemplate>

            </artina:BoolMemberTemplateSelector>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ScrollView
        Orientation="Both">
        <artina:GridOptionsView
            WidthRequest="320"
            Margin="0"
            Padding="10"
            ColumnSpacing="10"
            RowSpacing="10"
            ColumnCount="2"
            ItemsSource="{Binding DashboardTaskMultipleTilesList}"
            ItemTemplate="{StaticResource Selector}"
            />
    </ScrollView>
</ContentPage>

Информационная панель XAML.cs

using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;

namespace PapWine
{
    public partial class DashboardTaskMultipleTilesPage : ContentPage
    {
        public DashboardTaskMultipleTilesPage()
        {           
            InitializeComponent();
            NavigationPage.SetHasNavigationBar(this, true);
            BindingContext = new DashboardTaskMultipleTilesViewModel();  
        }

        protected override async void OnAppearing()
        {
            base.OnAppearing();

            PublicClientApplication PCA = new PublicClientApplication(Constants.ClientID, Constants.Authority);
            IEnumerable<IAccount> accounts = await PCA.GetAccountsAsync();
            AuthenticationResult authenticationResult = await PCA.AcquireTokenSilentAsync(Constants.Scopes, GetAccountByPolicy(accounts, Constants.PolicySignUpSignIn), Constants.Authority, false);
            JObject user = ParseIdToken(authenticationResult.IdToken);
            var currentuseroid = user["oid"]?.ToString();
        }

        private IAccount GetAccountByPolicy(IEnumerable<IAccount> accounts, string policy)
        {
            foreach (var account in accounts)
            {
                string userIdentifier = account.HomeAccountId.ObjectId.Split('.')[0];
                if (userIdentifier.EndsWith(policy.ToLower())) return account;
            }
            return null;
        }

        JObject ParseIdToken(string idToken)
        {
            // Get the piece with actual user info
            idToken = idToken.Split('.')[1];
            idToken = Base64UrlDecode(idToken);
            return JObject.Parse(idToken);
        }

        string Base64UrlDecode(string str)
        {
            str = str.Replace('-', '+').Replace('_', '/');
            str = str.PadRight(str.Length + (4 - str.Length % 4) % 4, '=');
            var byteArray = Convert.FromBase64String(str);
            var decoded = Encoding.UTF8.GetString(byteArray, 0, byteArray.Count());
            return decoded;
        }    
   }
}   

Модель представления информационной панели:

using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace PapWine
{
    public class DashboardTaskMultipleTilesViewModel : ObservableObject
    {
        private List<DashboardTaskMultipleTileItem> _dashboardTaskMultipleTilesList;

        public DashboardTaskMultipleTilesViewModel()
            : base(listenCultureChanges: true)
        {
            LoadData();
        }

        public List<DashboardTaskMultipleTileItem> DashboardTaskMultipleTilesList
        {
            get { return _dashboardTaskMultipleTilesList; }
            set { SetProperty(ref _dashboardTaskMultipleTilesList, value); }
        }

        protected override void OnCultureChanged(CultureInfo culture)
        {
            LoadData();
        }

        public async Task LogMeOut()
        {
            bool loggedOut = await App.AuthenticationProvider.LogoutAsync();

            if (loggedOut)
            {
                Application.Current.MainPage = new LandingPagePreLogin();
            }
        }

        private void LoadData()
        {
            DashboardTaskMultipleTilesList = new List<DashboardTaskMultipleTileItem> 
            {
                 //1 line
                new DashboardTaskMultipleTileItem
                {
                    Title = "Log Out",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeFont.Lock,
                    IconColour = "White"
                },
                new DashboardTaskMultipleTileItem
                {
                    Title = "User Settings",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeFont.Gear,
                    IconColour = "White"
                },
                 //2 line
                new DashboardTaskMultipleTileItem
                {
                    Title = "User Info",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeFont.User,
                    Badge = 12,
                    IconColour = "White"
                },

                new DashboardTaskMultipleTileItem
                {
                    Title = "Papillon Shop",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeWeb511Font.store,
                    Badge = 2,
                    IconColour = "White"
                },

                //3 line
                new DashboardTaskMultipleTileItem
                {
                    Title = "Check Bottles In",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeFont.Book,
                    IconColour = "White"
                },

                new DashboardTaskMultipleTileItem
                {
                    Title = "Lay Bottles Down",
                    Body = "",
                    Avatar = "",
                    BackgroundColor = "transparent",
                    ShowBackgroundColor = false,
                    IsNotification = false,
                    BackgroundImage = "Tiles/DarkBlackTile.jpg",
                    Icon = FontAwesomeFont.Bed,
                    Badge = 2,
                    IconColour = "White"
                },

            };
        }
    }

    public class DashboardTaskMultipleTileItem
    {
        public string Title { get; set; }
        public string Body { get; set; }
        public string Avatar { get; set; }
        public string BackgroundColor { get; set; }
        public string BackgroundImage { get; set; }
        public bool ShowBackgroundColor { get; set; }
        public bool IsNotification { get; set; }
        public string Icon { get; set; }
        public int Badge { get; set; }
        public string NavigPage { get; set; }


        private Xamarin.Forms.Command _tapCommand;
        public Xamarin.Forms.Command TapCommand
        {                

        get
            {
                if (_tapCommand == null)
                {
                    switch (this.Title) {

                        case "Log Out":
                            App.AuthenticationProvider.LogoutAsync();
                            _tapCommand = new Xamarin.Forms.Command(() =>
                            Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new LogoutSuccessPage()));
                            break;
                        case "User Settings":                            
                            _tapCommand = new Xamarin.Forms.Command(() =>
                             Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserSettingsPage()));
                            break;
                        case "User Info":
                            _tapCommand = new Xamarin.Forms.Command(() =>
                            Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserProfilePage()));
                            break;
                        case "Papillon Shop":
                            _tapCommand = new Xamarin.Forms.Command(() =>

                            Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new ProductFamilyMultipleTilesPage()));
                            break;
                        case "Check Bottles In":
                            _tapCommand = new Xamarin.Forms.Command(() =>

                            Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
                            break;
                        case "Check Bottles Out":
                            _tapCommand = new Xamarin.Forms.Command(() =>

                            Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
                            break;
                    } 

                  }

                return _tapCommand;
            }
        }   
    }    

}

Шаблон элемента плитки:

using Xamarin.Forms;

namespace PapWine
{
    public partial class TaskTilesItemTemplate : TaskTilesItemTemplateBase
    {

        public TaskTilesItemTemplate()
        {
            InitializeComponent();                        
        }

        private async void OnTileTapped(object sender, ItemTappedEventArgs e)
        {
            if (e.Item == null)
                return;
            var content = e.Item as DashboardTaskMultipleTileItem;
            //await Navigation.PushAsync(new LayBottlesDown()); //pass content if you want to pass the clicked item object to another page
        }       
   }

}

База шаблонов элементов плитки:

using System;
using System.Threading.Tasks;
using Xamarin.Forms;


namespace PapWine
{
    public class TaskTilesItemTemplateBase : ContentView
    {
        public uint animationDuration = 250;
        public bool _processingTag = false;

        public static readonly BindableProperty ShowBackgroundImageProperty =
            BindableProperty.Create(
                nameof(ShowBackgroundImage),
                typeof(bool),
                typeof(TaskTilesItemTemplate),
                true,
                defaultBindingMode: BindingMode.OneWay
            );

        public bool ShowBackgroundImage
        {
            get { return (bool)GetValue(ShowBackgroundImageProperty); }
            set { SetValue(ShowBackgroundImageProperty, value); }
        }

        public static readonly BindableProperty ShowBackgroundColorProperty =
            BindableProperty.Create (
                nameof( ShowBackgroundColor ), 
                typeof ( bool ),
                typeof ( TaskTilesItemTemplate ),
                false,
                defaultBindingMode  : BindingMode.OneWay
            );

        public bool ShowBackgroundColor {
            get { return ( bool )GetValue( ShowBackgroundColorProperty ); }
            set { SetValue ( ShowBackgroundColorProperty, value ); }
        }

        public static readonly BindableProperty ShowiconColoredCircleBackgroundProperty =
            BindableProperty.Create (
                nameof( ShowiconColoredCircleBackground ),
                typeof ( bool ),
                typeof (TaskTilesItemTemplate),
                true,
                defaultBindingMode  : BindingMode.OneWay
            );

        public bool ShowiconColoredCircleBackground {
            get { return ( bool )GetValue( ShowiconColoredCircleBackgroundProperty ); }
            set { SetValue ( ShowiconColoredCircleBackgroundProperty, value ); }
        }

        public static readonly BindableProperty TextColorProperty =
            BindableProperty.Create (
                nameof( TextColor ),
                typeof ( Color ),
                typeof (TaskTilesItemTemplate),
                defaultValue        : Color.White,
                defaultBindingMode  : BindingMode.OneWay
            );

        public Color TextColor {
            get { return ( Color )GetValue( TextColorProperty ); }
            set { SetValue ( TextColorProperty, value ); }
        }
        //added start
        public Color IconColour
        {
            get { return (Color)GetValue(TextColorProperty); }
            set { SetValue(TextColorProperty, value); }
        }
        //added end
        public async void OnWidgetTapped(object sender, EventArgs e)
        {
            if (_processingTag) 
            {
                return;
            }

            _processingTag = true;

            try{
                await AnimateItem (this, animationDuration  );

                await SamplesListFromCategoryPage.NavigateToCategory ((SampleCategory)BindingContext, Navigation);
            }finally{
                _processingTag = false;
            }
        }

        private async Task AnimateItem(View uiElement, uint duration ){
            var originalOpacity = uiElement.Opacity;

            await uiElement.FadeTo(.5, duration/2, Easing.CubicIn);
            await uiElement.FadeTo(originalOpacity, duration/2, Easing.CubicIn);

    }
}
}

LoginAsync выглядит так:

public async Task<bool> LoginAsync(bool useSilent = false)
        {
            bool success = false;
            //AuthenticationResult authResult = null;

            try
            {
                AuthenticationResult authenticationResult;

                if (useSilent)
                {
                    authenticationResult = await ADB2CClient.AcquireTokenSilentAsync(
                        Constants.Scopes,
                        GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
                        Constants.Authority,
                        false);
                    UpdateUserInfo(authenticationResult);
                }
                else
                {
                    authenticationResult = await ADB2CClient.AcquireTokenAsync(
                        Constants.Scopes,
                        GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
                        App.UiParent);
                }

                if (User == null)
                {
                    var payload = new JObject();
                    if (authenticationResult != null && !string.IsNullOrWhiteSpace(authenticationResult.IdToken))
                    {
                        payload["access_token"] = authenticationResult.IdToken;
                    }

                    User = await TodoItemManager.DefaultManager.CurrentClient.LoginAsync(
                        MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
                        payload);
                    success = true;
                }
            }

person Mark    schedule 12.01.2019    source источник
comment
почему Dashboard вызывает AcquireTokenSilentAsync? Каждая страница делает это?   -  person Jason    schedule 12.01.2019
comment
Tbh, это был просто тест, чтобы увидеть, смогу ли я ввести токен на страницу при появлении. Не сработало, надо было удалить, извините. Но проблема существовала до того, как я добавил метод onappearing()   -  person Mark    schedule 12.01.2019
comment
Хочу добавить, что это преследует меня уже 4 дня, и единственная разница, которую я вижу, это тот факт, что страница, которую я пытаюсь вставить, имеет структуру MVVM.   -  person Mark    schedule 12.01.2019
comment
откуда вы знаете, что вы больше не аутентифицированы? Это связано с тем, что запрос данных Azure не выполняется?   -  person Jason    schedule 13.01.2019
comment
Когда я нажимаю плитку Check Bottles In, я получаю сообщение об ошибке:   -  person Mark    schedule 13.01.2019
comment
Я понятия не имею, что делает Check Bottles In. Вам нужно опубликовать конкретную строку кода. И конкретное исключение, которое он генерирует.   -  person Jason    schedule 13.01.2019
comment
Когда я щелкаю плитку Check Bottles In, когда страница с вкладками загружается успешно, я получаю сообщение об ошибке: Microsoft.Identity.Client.MsalUiRequiredException: нулевая учетная запись была передана в AcquiretokenSilent API. Передайте объект учетной записи или вызовите методAcquireToken для аутентификации. Поэтому я вставил метод Onappearing. Я попытался поймать сообщение об ошибке при загрузке страницы, и это все еще было: Microsoft.Identity.Client.MsalUiRequiredException: нулевая учетная запись была передана в AcquiretokenSilent API. Передайте объект учетной записи или вызовите методAcquireToken для аутентификации.   -  person Mark    schedule 13.01.2019
comment
Извините, Джейсон, я слишком быстро нажал кнопку ввода, и он ввел комментарий.... затем потребовалось более 5 минут, чтобы запустить приложение, и поэтому я не смог изменить комментарий... аааааа.   -  person Mark    schedule 13.01.2019
comment
Проверка бутылок в нажатой плитке запускает этот код: ; Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn()); ломать;   -  person Mark    schedule 13.01.2019
comment
Тогда я бы предположил, что исключение происходит в конструкторе или OnAppearing SignBottlesIn. Делает ли какой-либо из этих методов что-нибудь с Auth? Вы пытались изучить трассировку стека исключения? Он должен сообщить вам, какой конкретный номер строки вызвал это. А вы гуглили MsalUiRequiredException? Первый хит, который я получил, был полон полезной информации.   -  person Jason    schedule 13.01.2019
comment
Но точно такой же метод onappearing работает на всех предыдущих страницах .... снова погуглите, как было предложено, но я уже пробовал это.   -  person Mark    schedule 13.01.2019
comment
Исключение вызвано вызовом AcquireTokenSilentAsync? Потому что самый первый вопрос, который я задал вам, был на каждой странице?   -  person Jason    schedule 13.01.2019
comment
Нет, каждая страница не делает этого. Я вставил это только для проверки исключения на этой конкретной странице. Поэтому, если я уберу это, страница загружается нормально, и я получаю исключение только при нажатии на плитку. Он отлично переходит на страницу с вкладками, но данные не вводятся, и при вызове данных срабатывает исключение. Если я вызываю точно ту же страницу со страницы ванильного контента с помощью кнопки, страница с вкладками загружается нормально, и данные поступают. мило. Так что определенно что-то происходит на странице плиток приборной панели.   -  person Mark    schedule 13.01.2019
comment
Давайте продолжим обсуждение в чате.   -  person Jason    schedule 13.01.2019
comment
Привет, Джейсон, дай мне знать, когда будешь свободен, я все приготовил.   -  person Mark    schedule 15.01.2019
comment
@Отметка. Не могли бы вы рассказать нам немного больше о том, что такое App.AuthenticationProvider.LoginAsync(). Это какой-то код, который вы написали? или фреймворк SDK, который вы использовали? Это реализовано с помощью MSAL?   -  person Jean-Marc Prieur    schedule 17.01.2019
comment
@Jean-MarcPrieur Hiya Я добавил LoginAsync() к вопросу (в конце). Это из одного из ваших образцов, постараюсь откопать какой ;-)   -  person Mark    schedule 17.01.2019