Condividi tramite


Creare un'app per l'accesso Windows Hello

Questa è la prima parte di una procedura dettagliata completa su come creare un'app di Windows in pacchetto che usa Windows Hello come alternativa ai tradizionali sistemi di autenticazione con nome utente e password. In questo caso, l'app è un'app WinUI, ma lo stesso approccio può essere usato con qualsiasi app di Windows in pacchetto, tra cui WPF e app Windows Form. L'app usa un nome utente per l'accesso e crea una chiave Hello per ogni account. Questi account verranno protetti dal PIN configurato in Impostazioni di Windows nella configurazione di Windows Hello.

Questa procedura dettagliata è suddivisa in due parti: la compilazione dell'app e la connessione del servizio back-end. Al termine di questo articolo, continuare con la parte 2: servizio di accesso di Windows Hello.

Prima di iniziare, è necessario leggere la panoramica di Windows Hello per una conoscenza generale del funzionamento di Windows Hello.

Operazioni preliminari

Per creare questo progetto, avrai bisogno di esperienza con C# e XAML. Dovrai anche usare Visual Studio 2022 in un computer Windows 10 o Windows 11. Per istruzioni complete sulla configurazione dell'ambiente di sviluppo, vedere Introduzione a WinUI .

  • In Visual Studio selezionare File>Nuovo>Progetto.
  • Nell'elenco a discesa Filtri della finestra di dialogo Nuovo progetto selezionare rispettivamente C#/C++, Windows e WinUI.
  • Scegliere App vuota, In pacchetto (WinUI 3 in Desktop) e assegnare all'applicazione il nome "WindowsHelloLogin".
  • Compilare ed eseguire la nuova applicazione (F5), verrà visualizzata una finestra vuota visualizzata sullo schermo. Chiudere l'applicazione.

Screenshot della nuova app di accesso di Windows Hello in esecuzione per la prima volta

Esercizio 1: Accedere con Windows Hello

In questo esercizio si apprenderà come verificare se Windows Hello è configurato nel computer e come accedere a un account usando Windows Hello.

  • Nel nuovo progetto creare una nuova cartella nella soluzione denominata "Views". Questa cartella conterrà le pagine che verranno spostate in questo esempio. Fare clic con il pulsante destro del mouse sul progetto in Esplora soluzioni, scegliere Aggiungi>nuova cartella e quindi rinominare la cartella in Visualizzazioni.

    Screenshot dell'aggiunta di una nuova cartella denominata Views al progetto di accesso di Windows Hello

  • Aprire MainWindow.xaml e sostituire il Window contenuto con un controllo o Grid vuotoStackPanel. Verrà implementato lo spostamento nella pagina e si passerà a una nuova pagina quando viene caricato MainWindow , quindi non è necessario alcun contenuto in MainWindow.

  • Aggiungere una Title proprietà a MainWindow in XAML. L'attributo dovrebbe essere simile al seguente: Title="Windows Hello Login".

  • Rimuovere il gestore eventi myButton_Click da MainWindow.xaml.cs per evitare errori di compilazione. Questo gestore eventi non è necessario per questo esempio.

  • Fare clic con il pulsante destro del mouse sulla nuova cartella Visualizzazioni, selezionare Aggiungi>nuovo elemento e selezionare il modello Pagina vuota. Assegnare alla pagina il nome "MainPage.xaml".

    Screenshot dell'aggiunta di una nuova pagina vuota al progetto di accesso di Windows Hello

  • Aprire il file App.xaml.cs e aggiornare il gestore OnLaunched per implementare lo spostamento di pagina per l'app. È anche necessario aggiungere un metodo del gestore RootFrame_NavigationFailed per gestire eventuali errori che si verificano durante il caricamento delle pagine.

    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
        var rootFrame = new Frame();
        rootFrame.NavigationFailed += RootFrame_NavigationFailed;
        rootFrame.Navigate(typeof(MainPage), args);
        m_window.Content = rootFrame;
        m_window.Activate();
    }
    
    private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
    {
        throw new Exception($"Error loading page {e.SourcePageType.FullName}");
    }
    
  • È anche necessario aggiungere quattro istruzioni using all'inizio del file App.xaml.cs per risolvere gli errori di compilazione nel codice.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using WindowsHelloLogin.Views;
    
  • Fare clic con il pulsante destro del mouse sulla nuova cartella Visualizzazioni, selezionare Aggiungi>nuovo elemento e selezionare il modello Pagina vuota. Assegnare alla pagina il nome "Login.xaml".

  • Per definire l'interfaccia utente per la nuova pagina di accesso, aggiungere il codice XAML seguente. Questo codice XAML definisce un oggetto StackPanel per allineare gli elementi figlio seguenti:

    • Oggetto TextBlock che conterrà un titolo.

    • Oggetto TextBlock per i messaggi di errore.

    • Oggetto TextBox per l'input del nome utente.

    • Oggetto Button per passare a una pagina di registrazione.

    • Oggetto TextBlock che contiene lo stato di Windows Hello.

    • Oggetto TextBlock per spiegare la pagina Di accesso, perché non sono ancora presenti utenti back-end o configurati.

      <Grid>
        <StackPanel>
          <TextBlock Text="Login" FontSize="36" Margin="4" TextAlignment="Center"/>
          <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
          <TextBlock Text="Enter your username below" Margin="0,0,0,20"
                     TextWrapping="Wrap" Width="300"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
          <Button x:Name="LoginButton" Content="Login" Background="DodgerBlue" Foreground="White"
                  Click="LoginButton_Click" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
          <TextBlock Text="Don't have an account?"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBlock x:Name="RegisterButtonTextBlock" Text="Register now"
                     PointerPressed="RegisterButtonTextBlock_OnPointerPressed"
                     Foreground="DodgerBlue"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <Border x:Name="WindowsHelloStatus" Background="#22B14C"
                  Margin="0,20" Height="100" >
            <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!"
                       Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
          </Border>
          <TextBlock x:Name="LoginExplanation" FontSize="24" TextAlignment="Center" TextWrapping="Wrap" 
                     Text="Please Note: To demonstrate a login, validation will only occur using the default username 'sampleUsername'"/>
        </StackPanel>
      </Grid>
      
  • Per ottenere la compilazione della soluzione, è necessario aggiungere alcuni metodi al file code-behind. Premere F7 o usare il Esplora soluzioni per modificare il file Login.xaml.cs. Aggiungere i due metodi di evento seguenti per gestire gli eventi Login e Register . Per il momento questi metodi impostano su ErrorMessage.Text una stringa vuota. Assicurarsi di includere le istruzioni using seguenti. Saranno necessari per i passaggi successivi.

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Input;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            public Login()
            {
                this.InitializeComponent();
            }
            private void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
        }
    }
    
  • Per eseguire il rendering della pagina Di accesso , modificare il codice MainPage per passare alla pagina Di accesso al caricamento di MainPage . Aprire il file MainPage.xaml.cs. In Esplora soluzioni fare doppio clic su MainPage.xaml.cs. Se non riesci a trovarlo, fai clic sulla piccola freccia accanto a MainPage.xaml per visualizzare il file code-behind. Creare un metodo del gestore eventi Loaded che passerà alla pagina Di accesso .

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • Nella pagina Account di accesso è necessario gestire l'evento OnNavigatedTo per verificare se Windows Hello è disponibile nel computer corrente. In Login.xaml.cs implementare il codice seguente. Si noterà che l'oggetto WindowsHelloHelper indica che si è verificato un errore. Questo perché non è ancora stata creata questa classe helper.

    public sealed partial class Login : Page
    {
        public Login()
        {
            this.InitializeComponent();
        }
    
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            // Check if Windows Hello is set up and available on this machine
            if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
            {
            }
            else
            {
                // Windows Hello isn't set up, so inform the user
                WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                LoginButton.IsEnabled = false;
            }
        }
    }
    
  • Per creare la classe WindowsHelloHelper, fare clic con il pulsante destro del mouse sul progetto WindowsHelloLogin e scegliere Aggiungi>nuova cartella. Assegnare alla cartella il nome Utils.

  • Fare clic con il pulsante destro del mouse sulla cartella Utils e selezionare Aggiungi>classe. Assegnare alla nuova classe il nome "WindowsHelloHelper.cs".

    Screenshot della creazione della classe helper di accesso di Windows Hello

  • Modificare l'ambito della classe public staticWindowsHelloHelper come , quindi aggiungere il metodo seguente per informare l'utente se Windows Hello è pronto per essere usato o meno. Sarà necessario aggiungere gli spazi dei nomi necessari.

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class WindowsHelloHelper
        {
            /// <summary>
            /// Checks to see if Windows Hello is ready to be used.
            /// 
            /// Windows Hello has dependencies on:
            ///     1. Having a connected Microsoft Account
            ///     2. Having a Windows PIN set up for that account on the local machine
            /// </summary>
            public static async Task<bool> WindowsHelloAvailableCheckAsync()
            {
                bool keyCredentialAvailable = await KeyCredentialManager.IsSupportedAsync();
                if (keyCredentialAvailable == false)
                {
                    // Key credential is not enabled yet as user 
                    // needs to connect to a Microsoft Account and select a PIN in the connecting flow.
                    Debug.WriteLine("Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.");
                    return false;
                }
    
                return true;
            }
        }
    }
    
  • In Login.xaml.cs aggiungere un riferimento allo spazio dei WindowsHelloLogin.Utils nomi . Verrà risolto l'errore nel OnNavigatedTo metodo .

    using WindowsHelloLogin.Utils;
    
  • Compilare ed eseguire l'applicazione. Si passerà alla pagina di accesso e il banner di Windows Hello indicherà se Windows Hello è pronto per l'uso. Verrà visualizzato il banner verde o blu che indica lo stato di Windows Hello nel computer.

    Screenshot della schermata di accesso di Windows Hello con lo stato pronto

  • L'operazione successiva da eseguire consiste nel creare la logica per l'accesso. Creare una nuova cartella nel progetto denominato "Models".

  • Nella cartella Models creare una nuova classe denominata "Account.cs". Questa classe fungerà da modello di account. Poiché si tratta di un progetto di esempio, conterrà solo un nome utente. Modificare l'ambito della classe in public e aggiungere la Username proprietà .

    namespace WindowsHelloLogin.Models
    {
        public class Account
        {
            public string Username { get; set; }
        }
    }
    
  • L'app deve gestire gli account. Per questo lab pratico, poiché non è presente alcun server o database, un elenco di utenti viene salvato e caricato localmente. Fare clic con il pulsante destro del mouse sulla cartella Utils e aggiungere una nuova classe denominata "AccountHelper.cs". Modificare l'ambito della classe in modo che sia public static. AccountHelper è una classe statica che contiene tutti i metodi necessari per salvare e caricare l'elenco di account in locale. Il salvataggio e il caricamento funziona usando un xmlSerializer. È anche necessario ricordare il file salvato e il percorso in cui è stato salvato.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    using WindowsHelloLogin.Models;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class AccountHelper
        {
            // In the real world this would not be needed as there would be a server implemented that would host a user account database.
            // For this tutorial we will just be storing accounts locally.
            private const string USER_ACCOUNT_LIST_FILE_NAME = "accountlist.txt";
            private static string _accountListPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            public static List<Account> AccountList = [];
    
            /// <summary>
            /// Create and save a useraccount list file. (Updating the old one)
            /// </summary>
            private static async void SaveAccountListAsync()
            {
                string accountsXml = SerializeAccountListToXml();
    
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
                else
                {
                    StorageFile accountsFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(USER_ACCOUNT_LIST_FILE_NAME);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
            }
    
            /// <summary>
            /// Gets the useraccount list file and deserializes it from XML to a list of useraccount objects.
            /// </summary>
            /// <returns>List of useraccount objects</returns>
            public static async Task<List<Account>> LoadAccountListAsync()
            {
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
    
                    string accountsXml = await FileIO.ReadTextAsync(accountsFile);
                    DeserializeXmlToAccountList(accountsXml);
                }
    
                return AccountList;
            }
    
            /// <summary>
            /// Uses the local list of accounts and returns an XML formatted string representing the list
            /// </summary>
            /// <returns>XML formatted list of accounts</returns>
            public static string SerializeAccountListToXml()
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                var writer = new StringWriter();
                xmlizer.Serialize(writer, AccountList);
    
                return writer.ToString();
            }
    
            /// <summary>
            /// Takes an XML formatted string representing a list of accounts and returns a list object of accounts
            /// </summary>
            /// <param name="listAsXml">XML formatted list of accounts</param>
            /// <returns>List object of accounts</returns>
            public static List<Account> DeserializeXmlToAccountList(string listAsXml)
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
    
                return AccountList = (xmlizer.Deserialize(textreader)) as List<Account>;
            }
        }
    }
    
  • Implementare quindi un modo per aggiungere e rimuovere un account dall'elenco locale di account. Queste azioni salveranno ogni elenco. Il metodo finale necessario per questo lab pratico è un metodo di convalida. Poiché non esiste un server di autorizzazione o un database di utenti, verrà convalidato in base a un singolo utente hardcoded. Questi metodi devono essere aggiunti alla classe AccountHelper .

    public static Account AddAccount(string username)
    {
        // Create a new account with the username
        var account = new Account() { Username = username };
        // Add it to the local list of accounts
        AccountList.Add(account);
        // SaveAccountList and return the account
        SaveAccountListAsync();
        return account;
    }
    
    public static void RemoveAccount(Account account)
    {
        // Remove the account from the accounts list
        AccountList.Remove(account);
        // Re save the updated list
        SaveAccountListAsync();
    }
    
    public static bool ValidateAccountCredentials(string username)
    {
        // In the real world, this method would call the server to authenticate that the account exists and is valid.
        // However, for this tutorial, we'll just have an existing sample user that's named "sampleUsername".
        // If the username is null or does not match "sampleUsername" validation will fail. 
        // In this case, the user should register a new Windows Hello user.
    
        if (string.IsNullOrEmpty(username))
        {
            return false;
        }
    
        if (!string.Equals(username, "sampleUsername"))
        {
            return false;
        }
    
        return true;
    }
    
  • L'operazione successiva da eseguire consiste nell'gestire una richiesta di accesso da parte dell'utente. In Login.xaml.cs creare una nuova variabile privata che conterrà l'accesso all'account corrente. Aggiungere quindi un nuovo metodo denominato SignInWindowsHelloAsync. In questo modo le credenziali dell'account verranno convalidate usando il metodo AccountHelper.ValidateAccountCredentials . Questo metodo restituirà un valore booleano se il nome utente immesso corrisponde al valore stringa hardcoded configurato nel passaggio precedente. Il valore hardcoded per questo esempio è "sampleUsername".

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check if Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = "Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
    
            private async void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                await SignInWindowsHelloAsync();
            }
    
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
    
            private async Task SignInWindowsHelloAsync()
            {
                if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
                {
                    // Create and add a new local account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
                    //if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
                    //{
                    //    Debug.WriteLine("Successfully signed in with Windows Hello!");
                    //}
                }
                else
                {
                    ErrorMessage.Text = "Invalid Credentials";
                }
            }
        }
    }
    
  • Potresti aver notato il codice commentato che fa riferimento a un metodo in WindowsHelloHelper. In WindowsHelloHelper.cs aggiungere un nuovo metodo denominato CreateWindowsHelloKeyAsync. Questo metodo usa l'API Windows Hello in KeyCredentialManager. La chiamata a RequestCreateAsync creerà una chiave di Windows Hello specifica dell'accountId e del computer locale. Si notino i commenti nell'istruzione switch se si è interessati a implementare questo in uno scenario reale.

    /// <summary>
    /// Creates a Windows Hello key on the machine using the account ID provided.
    /// </summary>
    /// <param name="accountId">The account ID associated with the account that we are enrolling into Windows Hello</param>
    /// <returns>Boolean indicating if creating the Windows Hello key succeeded</returns>
    public static async Task<bool> CreateWindowsHelloKeyAsync(string accountId)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(accountId, KeyCredentialCreationOption.ReplaceExisting);
    
        switch (keyCreationResult.Status)
        {
            case KeyCredentialStatus.Success:
                Debug.WriteLine("Successfully created key");
    
                // In the real world, authentication would take place on a server.
                // So, every time a user migrates or creates a new Windows Hello
                // account, details should be pushed to the server.
                // The details that would be pushed to the server include:
                // The public key, keyAttestation (if available), 
                // certificate chain for attestation endorsement key (if available),  
                // status code of key attestation result: keyAttestationIncluded or 
                // keyAttestationCanBeRetrievedLater and keyAttestationRetryType.
                // As this sample has no concept of a server, it will be skipped for now.
                // For information on how to do this, refer to the second sample.
    
                // For this sample, just return true
                return true;
            case KeyCredentialStatus.UserCanceled:
                Debug.WriteLine("User cancelled sign-in process.");
                break;
            case KeyCredentialStatus.NotFound:
                // User needs to set up Windows Hello
                Debug.WriteLine("Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • Dopo aver creato il metodo CreateWindowsHelloKeyAsync , tornare al file Login.xaml.cs e rimuovere il commento dal codice all'interno del metodo SignInWindowsHelloAsync.

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compilare ed eseguire l'applicazione. Verrà visualizzata la pagina di accesso. Immettere il nome utente "sampleUsername" e fare clic su login. Verrà richiesto un prompt di Windows Hello che chiede di immettere il PIN. Quando si immette correttamente il PIN, il metodo CreateWindowsHelloKeyAsync sarà in grado di creare una chiave di Windows Hello. Monitorare le finestre di output per verificare se vengono visualizzati i messaggi che indicano l'esito positivo.

    Screenshot del prompt del pin di accesso di Windows Hello

Esercizio 2: Pagine di benvenuto e selezione utente

In questo esercizio si continuerà dall'esercizio precedente. Quando un utente accede correttamente, deve essere visualizzato in una pagina di benvenuto in cui può disconnettersi o eliminare il proprio account. Quando Windows Hello crea una chiave per ogni computer, è possibile creare una schermata di selezione utente, che visualizza tutti gli utenti che hanno eseguito l'accesso a tale computer. Un utente può quindi selezionare uno di questi account e passare direttamente alla schermata iniziale senza dover immettere nuovamente una password perché è già stata autenticata per accedere al computer.

  • Nella cartella Views aggiungere una nuova pagina vuota denominata "Welcome.xaml". Aggiungere il codice XAML seguente per completare l'interfaccia utente per la pagina. Verrà visualizzato un titolo, il nome utente connesso e due pulsanti. Uno dei pulsanti tornerà a un elenco di utenti (che verrà creato in un secondo momento) e l'altro pulsante gestirà l'oblio di questo utente.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Welcome" FontSize="40" TextAlignment="Center"/>
        <TextBlock x:Name="UserNameText" FontSize="28" TextAlignment="Center"/>
    
        <Button x:Name="BackToUserListButton" Content="Back to User List" Click="Button_Restart_Click"
                HorizontalAlignment="Center" Margin="0,20" Foreground="White" Background="DodgerBlue"/>
    
        <Button x:Name="ForgetButton" Content="Forget Me" Click="Button_Forget_User_Click"
                Foreground="White"
                Background="Gray"
                HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • Nel file code-behind Welcome.xaml.cs aggiungere una nuova variabile privata che conterrà l'account connesso. Sarà necessario implementare un metodo per eseguire l'override dell'evento OnNavigateTo , che archivierà l'account passato alla pagina di benvenuto . Dovrai anche implementare l'evento Click per i due pulsanti definiti in XAML. Sarà necessario aggiungere istruzioni using per gli spazi dei WindowsHelloLogin.Models nomi e WindowsHelloLogin.Utils .

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Welcome : Page
        {
            private Account _activeAccount;
    
            public Welcome()
            {
                InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                _activeAccount = (Account)e.Parameter;
                if (_activeAccount != null)
                {
                    UserNameText.Text = _activeAccount.Username;
                }
            }
    
            private void Button_Restart_Click(object sender, RoutedEventArgs e)
            {
            }
    
            private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
            {
                // Remove the account from Windows Hello
                // WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
                // Remove it from the local accounts list and re-save the updated list
                AccountHelper.RemoveAccount(_activeAccount);
    
                Debug.WriteLine($"User {_activeAccount.Username} deleted.");
            }
        }
    }
    
  • Potresti aver notato una riga impostata come commento nel Button_Forget_User_Click gestore eventi. L'account viene rimosso dall'elenco locale, ma attualmente non è possibile rimuoverlo da Windows Hello. È necessario implementare un nuovo metodo in WindowsHelloHelper.cs che gestirà la rimozione di un utente di Windows Hello. Questo metodo userà altre API di Windows Hello per aprire ed eliminare l'account. Nel mondo reale, quando si elimina un account il server o il database deve ricevere una notifica in modo che il database utente rimanga valido. Sarà necessaria un'istruzione using che fa riferimento allo spazio dei WindowsHelloLogin.Models nomi.

    using WindowsHelloLogin.Models;
    
    /// <summary>
    /// Function to be called when user requests deleting their account.
    /// Checks the KeyCredentialManager to see if there is a Windows Hello
    /// account for the current user.
    /// It then deletes the local key associated with the account.
    /// </summary>
    public static async void RemoveWindowsHelloAccountAsync(Account account)
    {
        // Open the account with Windows Hello
        KeyCredentialRetrievalResult keyOpenResult = await KeyCredentialManager.OpenAsync(account.Username);
    
        if (keyOpenResult.Status == KeyCredentialStatus.Success)
        {
            // In the real world you would send key information to server to unregister
            //for example, RemoveWindowsHelloAccountOnServer(account);
        }
    
        // Then delete the account from the machine's list of Windows Hello accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • Tornare Welcome.xaml.cs rimuovere il commento dalla riga che chiama RemoveWindowsHelloAccountAsync.

    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    }
    
  • Nel metodo SignInWindowsHelloAsync (in Login.xaml.cs), una volta completata l'operazione CreateWindowsHelloKeyAsync, passare alla pagina iniziale e passare l'account.

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            // Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compilare ed eseguire l'applicazione. Accedere con "sampleUsername" e fare clic su Account di accesso. Immettere il PIN e, se l'operazione ha esito positivo, passare alla schermata iniziale . Provare a fare clic su Dimentica utente e monitorare la finestra Output di Visual Studio per verificare se l'utente è stato eliminato. Si noti che quando l'utente viene eliminato, si rimane nella pagina di benvenuto . Dovrai creare una pagina di selezione utente in cui l'app può spostarsi.

    Screenshot della schermata iniziale di Windows Hello

  • Nella cartella Views creare una nuova pagina vuota denominata "UserSelection.xaml" e aggiungere il codice XAML seguente per definire l'interfaccia utente. Questa pagina conterrà un controllo ListView che visualizza tutti gli utenti nell'elenco degli account locali e un oggetto Button che passerà alla pagina Di accesso per consentire all'utente di aggiungere un altro account.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Select a User" FontSize="36" Margin="4" TextAlignment="Center" HorizontalAlignment="Center"/>
    
        <ListView x:Name="UserListView" Margin="4" MaxHeight="200" MinWidth="250" Width="250" HorizontalAlignment="Center">
          <ListView.ItemTemplate>
            <DataTemplate>
              <Grid Background="DodgerBlue" Height="50" Width="250" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <TextBlock Text="{Binding Username}" HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
              </Grid>
            </DataTemplate>
          </ListView.ItemTemplate>
        </ListView>
    
        <Button x:Name="AddUserButton" Content="+" FontSize="36" Width="60" Click="AddUserButton_Click" HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • In UserSelection.xaml.cs implementare il Loaded metodo che passerà alla pagina Di accesso se non sono presenti account nell'elenco locale. Implementare anche l'evento SelectionChanged per ListView e un Click evento per .Button

    using System.Diagnostics;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class UserSelection : Page
        {
            public UserSelection()
            {
                InitializeComponent();
                Loaded += UserSelection_Loaded;
            }
    
            private void UserSelection_Loaded(object sender, RoutedEventArgs e)
            {
                if (AccountHelper.AccountList.Count == 0)
                {
                    // If there are no accounts, navigate to the Login page
                    Frame.Navigate(typeof(Login));
                }
    
    
                UserListView.ItemsSource = AccountHelper.AccountList;
                UserListView.SelectionChanged += UserSelectionChanged;
            }
    
            /// <summary>
            /// Function called when an account is selected in the list of accounts
            /// Navigates to the Login page and passes the chosen account
            /// </summary>
            private void UserSelectionChanged(object sender, RoutedEventArgs e)
            {
                if (((ListView)sender).SelectedValue != null)
                {
                    Account account = (Account)((ListView)sender).SelectedValue;
                    if (account != null)
                    {
                        Debug.WriteLine($"Account {account.Username} selected!");
                    }
                    Frame.Navigate(typeof(Login), account);
                }
            }
    
            /// <summary>
            /// Function called when the "+" button is clicked to add a new user.
            /// Navigates to the Login page with nothing filled out
            /// </summary>
            private void AddUserButton_Click(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • Nell'app sono presenti alcuni punti in cui si vuole passare alla pagina UserSelection . In MainPage.xaml.cs è necessario passare alla pagina UserSelection anziché alla pagina Di accesso . Mentre si è nell'evento caricato in MainPage, sarà necessario caricare l'elenco degli account in modo che la pagina UserSelection possa verificare se sono presenti account. Ciò richiederà la modifica del Loaded metodo come asincrono e l'aggiunta di un'istruzione using per lo spazio dei WindowsHelloLogin.Utils nomi .

    using WindowsHelloLogin.Utils;
    
    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Load the local account list before navigating to the UserSelection page
        await AccountHelper.LoadAccountListAsync();
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Successivamente, l'app dovrà passare alla pagina UserSelection dalla pagina iniziale . In entrambi Click gli eventi è necessario tornare alla pagina UserSelection .

    private void Button_Restart_Click(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
        // Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Nella pagina Account di accesso è necessario il codice per accedere all'account selezionato dall'elenco nella pagina UserSelection. In caso, archiviare l'account OnNavigatedTo passato durante la navigazione. Per iniziare, aggiungere una nuova variabile privata che identificherà se l'account è un account esistente. Gestire quindi l'evento OnNavigatedTo .

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
            private bool _isExistingAccount;
    
            public Login()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// Function called when this frame is navigated to.
            /// Checks to see if Windows Hello is available and if an account was passed in.
            /// If an account was passed in set the "_isExistingAccount" flag to true and set the _account.
            /// </summary>
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                    if (e.Parameter != null)
                    {
                        _isExistingAccount = true;
                        // Set the account to the existing account being passed in
                        _account = (Account)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        await SignInWindowsHelloAsync();
                    }
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
        }
    }
    
  • Il metodo SignInWindowsHelloAsync dovrà essere aggiornato per accedere all'account selezionato. WindowsHelloHelper richiederà un altro metodo per aprire l'account con Windows Hello, perché per l'account è già stata creata una chiave dell'account. Implementare il nuovo metodo in WindowsHelloHelper.cs per accedere a un utente esistente con Windows Hello. Per informazioni su ogni parte del codice, leggere i commenti sul codice.

    /// <summary>
    /// Attempts to sign a message using the account key on the system for the accountId passed.
    /// </summary>
    /// <returns>Boolean representing if creating the Windows Hello authentication message succeeded</returns>
    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(Account account)
    {
        KeyCredentialRetrievalResult openKeyResult = await KeyCredentialManager.OpenAsync(account.Username);
        // Calling OpenAsync will allow the user access to what is available in the app and will not require user credentials again.
        // If you wanted to force the user to sign in again you can use the following:
        // var consentResult = await Windows.Security.Credentials.UI.UserConsentVerifier.RequestVerificationAsync(account.Username);
        // This will ask for the either the password of the currently signed in Microsoft Account or the PIN used for Windows Hello.
    
        if (openKeyResult.Status == KeyCredentialStatus.Success)
        {
            // If OpenAsync has succeeded, the next thing to think about is whether the client application requires access to backend services.
            // If it does here you would request a challenge from the server. The client would sign this challenge and the server
            // would check the signed challenge. If it is correct, it would allow the user access to the backend.
            // You would likely make a new method called RequestSignAsync to handle all this.
            // For example, RequestSignAsync(openKeyResult);
            // Refer to the second Windows Hello sample for information on how to do this.
    
            // For this sample, there is not concept of a server implemented so just return true.
            return true;
        }
        else if (openKeyResult.Status == KeyCredentialStatus.NotFound)
        {
            // If the account is not found at this stage. It could be one of two errors. 
            // 1. Windows Hello has been disabled
            // 2. Windows Hello has been disabled and re-enabled cause the Windows Hello Key to change.
            // Calling CreateWindowsHelloKeyAsync and passing through the account will attempt to replace the existing Windows Hello Key for that account.
            // If the error really is that Windows Hello is disabled then the CreateWindowsHelloKeyAsync method will output that error.
            if (await CreateWindowsHelloKeyAsync(account.Username))
            {
                // If the Hello Key was again successfully created, Windows Hello has just been reset.
                // Now that the Hello Key has been reset for the account retry sign in.
                return await GetWindowsHelloAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Windows Hello right now, try again later
        return false;
    }
    
  • Aggiornare il metodo SignInWindowsHelloAsync in Login.xaml.cs per gestire l'account esistente. Verrà usato il nuovo metodo nel WindowsHelloHelper.cs. In caso di esito positivo, l'account verrà eseguito l'accesso e l'utente passerà alla pagina di benvenuto .

    private async Task SignInWindowsHelloAsync()
    {
        if (_isExistingAccount)
        {
            if (await WindowsHelloHelper.GetWindowsHelloAuthenticationMessageAsync(_account))
            {
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compilare ed eseguire l'applicazione. Accedere con "sampleUsername". Digitare il PIN e, se l'operazione riesce, verrà visualizzata la pagina iniziale . Fare clic di nuovo sull'elenco utenti. Si dovrebbe ora vedere un utente nell'elenco. Se si fa clic su questo, WindowsHello consente di accedere di nuovo senza dover immettere nuovamente password e così via.

    Screenshot dell'elenco di utenti di selezione di Windows Hello

Esercizio 3: Registrazione di un nuovo utente di Windows Hello

In questo esercizio viene creata una nuova pagina in grado di creare un nuovo account con Windows Hello. Questa operazione funziona in modo analogo al funzionamento della pagina Di accesso . La pagina Di accesso viene implementata per un utente esistente che esegue la migrazione per l'uso di Windows Hello. Una pagina WindowsHelloRegister creerà la registrazione di Windows Hello per un nuovo utente.

  • Nella cartella Views creare una nuova pagina vuota denominata "WindowsHelloRegister.xaml". Nel codice XAML aggiungere quanto segue per configurare l'interfaccia utente. L'interfaccia in questa pagina è simile alla pagina Account di accesso .

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Register New Windows Hello User" FontSize="24" Margin="4" TextAlignment="Center"/>
    
        <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
    
        <TextBlock Text="Enter your new username below" Margin="0,0,0,20"
                   TextWrapping="Wrap" Width="300"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    
        <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
    
        <Button x:Name="RegisterButton" Content="Register" Background="DodgerBlue" Foreground="White"
                Click="RegisterButton_Click_Async" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
    
        <Border x:Name="WindowsHelloStatus" Background="#22B14C" Margin="4" Height="100">
          <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!" FontSize="20"
                     Margin="4" TextAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </StackPanel>
    </Grid>
    
  • Nel file code-behind WindowsHelloRegister.xaml.cs implementare una variabile privata Account e un Click evento per il pulsante di registrazione. Verrà aggiunto un nuovo account locale e verrà creata una chiave di Windows Hello.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class WindowsHelloRegister : Page
        {
            private Account _account;
    
            public WindowsHelloRegister()
            {
                InitializeComponent();
            }
    
            private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
    
                // In the real world, you would validate the entered credentials and information before 
                // allowing a user to register a new account. 
                // For this sample, we'll skip that step and just register an account if the username is not null.
    
                if (!string.IsNullOrEmpty(UsernameTextBox.Text))
                {
                    // Register a new account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    // Register new account with Windows Hello
                    await WindowsHelloHelper.CreateWindowsHelloKeyAsync(_account.Username);
                    // Navigate to the Welcome page. 
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    ErrorMessage.Text = "Please enter a username";
                }
            }
        }
    }
    
  • È necessario passare a questa pagina dalla pagina Di accesso quando si fa clic su Registra.

    private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        ErrorMessage.Text = "";
        Frame.Navigate(typeof(WindowsHelloRegister));
    }
    
  • Compilare ed eseguire l'applicazione. Provare a registrare un nuovo utente. Tornare quindi all'elenco di utenti e verificare che sia possibile selezionare l'utente e l'accesso.

    Screenshot della pagina di registrazione di un nuovo utente in Windows Hello

In questo lab sono state apprese le competenze essenziali necessarie per usare la nuova API di Windows Hello per autenticare gli utenti esistenti e creare account per i nuovi utenti. Con questa nuova conoscenza, è possibile iniziare a rimuovere la necessità che gli utenti ricordino le password per l'applicazione, ma rimangono sicuri che le applicazioni rimangano protette dall'autenticazione utente. Windows usa la nuova tecnologia di autenticazione di Windows Hello per supportare le opzioni di accesso biometriche.