Condividi tramite


Creare un servizio di accesso Windows Hello

Questa è la seconda parte di una procedura dettagliata completa su come usare Windows Hello come alternativa ai tradizionali sistemi di autenticazione con nome utente e password nelle app di Windows in pacchetto. Questo articolo riprende da dove si era interrotta la Parte 1 dell'app di accesso a Windows Hello ed estende la funzionalità per dimostrare come integrare Windows Hello nell'applicazione esistente.

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 .

Esercizio 1: Logica lato server

In questo esercizio si inizia con l'applicazione Windows Hello compilata nel primo lab e si crea un server e un database fittizi locali. Questo lab pratico è progettato per insegnare come Windows Hello può essere integrato in un sistema esistente. Usando un server fittizio e un database fittizio, viene eliminata una grande quantità di installazione non correlata. Nelle applicazioni personalizzate sarà necessario sostituire gli oggetti fittizi con i servizi e i database reali.

  • Per iniziare, aprire la soluzione WindowsHelloLogin dal primo windows Hello Hands On Lab.

  • Inizierai implementando il server fittizio e il database fittizio. Creare una nuova cartella denominata "AuthService". In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto WindowsHelloLogin e scegliere Aggiungi>nuova cartella.

  • Creare classi UserAccount e WindowsHelloDevices che fungeranno da modelli per i dati da salvare nel database fittizio. UserAccount sarà simile al modello utente implementato in un server di autenticazione tradizionale. Fare clic con il pulsante destro del mouse sulla cartella AuthService e aggiungere una nuova classe denominata "UserAccount".

    Screenshot della creazione della cartella di autorizzazione di Windows Hello

    Screenshot della creazione della nuova classe per l'autorizzazione utente di Windows Hello

  • Modificare l'ambito della classe in modo che sia pubblico e aggiungere le proprietà pubbliche seguenti per la classe UserAccount . Sarà necessario aggiungere un'istruzione using per lo spazio dei System.ComponentModel.DataAnnotations nomi .

    using System;
    using System.ComponentModel.DataAnnotations;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class UserAccount
        {
            [Key, Required]
            public Guid UserId { get; set; }
            [Required]
            public string Username { get; set; }
            public string Password { get; set; }
            // public List<WindowsHelloDevice> WindowsHelloDevices = new();
        }
    }
    

    Potresti aver notato l'elenco commentato di WindowsHelloDevices. Questa è una modifica che dovrai apportare a un modello utente esistente nella tua attuale implementazione. L'elenco di WindowsHelloDevices conterrà un ID dispositivo, la chiave pubblica creata da Windows Hello e keyCredentialAttestationResult. Per questo esercizio, sarà necessario implementare keyAttestationResult perché vengono forniti solo da Windows Hello nei dispositivi che dispongono di un chip TPM (Trusted Platform Modules). KeyCredentialAttestationResult è una combinazione di più proprietà e dovrebbe essere divisa per salvarle e caricarle con un database.

  • Creare una nuova classe nella cartella AuthService denominata "WindowsHelloDevice.cs". Questo è il modello per i dispositivi Windows Hello come discusso sopra. Modificare l'ambito della classe in modo che sia pubblico e aggiungere le proprietà seguenti.

    using System;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class WindowsHelloDevice
        {
            // These are the new variables that will need to be added to the existing UserAccount in the Database
            // The DeviceName is used to support multiple devices for the one user.
            // This way the correct public key is easier to find as a new public key is made for each device.
            // The KeyAttestationResult is only used if the User device has a TPM (Trusted Platform Module) chip, 
            // in most cases it will not. So will be left out for this hands on lab.
            public Guid DeviceId { get; set; }
            public byte[] PublicKey { get; set; }
            // public KeyCredentialAttestationResult KeyAttestationResult { get; set; }
        }
    }
    
  • Tornare a UserAccount.cs e rimuovere il commento dall'elenco dei dispositivi Windows Hello.

    using System.Collections.Generic;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class UserAccount
        {
            [Key, Required]
            public Guid UserId { get; set; }
            [Required]
            public string Username { get; set; }
            public string Password { get; set; }
            public List<WindowsHelloDevice> WindowsHelloDevices = new();
        }
    }
    
  • Con il modello per UserAccount e WindowsHelloDevice creato, è necessario creare un'altra nuova classe nella cartella AuthService che fungerà da database fittizio, in quanto si tratta di un database fittizio da cui verrà salvato e caricato un elenco di account utente in locale. Nel mondo reale, si tratta dell'implementazione del database. Creare una nuova classe nella cartella AuthService denominata "MockStore.cs". Modificare l'ambito della classe in pubblico.

  • Poiché l'archivio fittizio salverà e caricherà un elenco di account utente in locale, è possibile implementare la logica per salvare e caricare tale elenco usando un XmlSerializer. Dovrai anche ricordare il nome del file e il percorso di salvataggio. In MockStore.cs implementare quanto segue:

    using System.Collections.Generic;
    using System;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    
    namespace WindowsHelloLogin.AuthService
    {
        public class MockStore
        {
            private const string USER_ACCOUNT_LIST_FILE_NAME = "userAccountsList.txt";
            // This cannot be a const because the LocalFolder is accessed at runtime
            private string _userAccountListPath = Path.Combine(
                ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            private List<UserAccount> _mockDatabaseUserAccountsList;
    
    #region Save and Load Helpers
            /// <summary>
            /// Create and save a useraccount list file. (Replacing the old one)
            /// </summary>
            private async Task SaveAccountListAsync()
            {
                string accountsXml = SerializeAccountListToXml();
    
                if (File.Exists(_userAccountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
                    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>
            private async Task LoadAccountListAsync()
            {
                if (File.Exists(_userAccountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
    
                    string accountsXml = await FileIO.ReadTextAsync(accountsFile);
                    DeserializeXmlToAccountList(accountsXml);
                }
    
                // If the UserAccountList does not contain the sampleUser Initialize the sample users
                // This is only needed as it in a Hand on Lab to demonstrate a user being migrated.
                // In the real world, user accounts would just be in a database.
                if (!_mockDatabaseUserAccountsList.Any(f => f.Username.Equals("sampleUsername")))
                {
                    //If the list is empty, call InitializeSampleAccounts and return the list
                    //await InitializeSampleUserAccountsAsync();
                }
            }
    
            /// <summary>
            /// Uses the local list of accounts and returns an XML formatted string representing the list
            /// </summary>
            /// <returns>XML formatted list of accounts</returns>
            private string SerializeAccountListToXml()
            {
                var xmlizer = new XmlSerializer(typeof(List<UserAccount>));
                var writer = new StringWriter();
                xmlizer.Serialize(writer, _mockDatabaseUserAccountsList);
                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>
            private List<UserAccount> DeserializeXmlToAccountList(string listAsXml)
            {
                var xmlizer = new XmlSerializer(typeof(List<UserAccount>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
                return _mockDatabaseUserAccountsList = (xmlizer.Deserialize(textreader)) as List<UserAccount>;
            }
    #endregion
        }
    }
    
  • Nel metodo LoadAccountListAsync è possibile che sia stato impostato come commento un metodo InitializeSampleUserAccountsAsync. Sarà necessario creare questo metodo nella MockStore.cs. Questo metodo popolerà l'elenco degli account utente in modo che possa avvenire un accesso. Nel mondo reale, il database utente sarebbe già popolato. In questo passaggio verrà creato anche un costruttore che inizializzerà l'elenco utenti e chiamerà LoadAccountListAsync.

    namespace WindowsHelloLogin.AuthService
    {
        public class MockStore
        {
            private const string USER_ACCOUNT_LIST_FILE_NAME = "userAccountsList.txt";
            // This cannot be a const because the LocalFolder is accessed at runtime
            private string _userAccountListPath = Path.Combine(
                ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            private List<UserAccount> _mockDatabaseUserAccountsList;
    
            public MockStore()
            {
                _mockDatabaseUserAccountsList = new List<UserAccount>();
                _ = LoadAccountListAsync();
            }
    
            private async Task InitializeSampleUserAccountsAsync()
            {
                // Create a sample Traditional User Account that only has a Username and Password
                // This will be used initially to demonstrate how to migrate to use Windows Hello
    
                var sampleUserAccount = new UserAccount()
                {
                    UserId = Guid.NewGuid(),
                    Username = "sampleUsername",
                    Password = "samplePassword",
                };
    
                // Add the sampleUserAccount to the _mockDatabase
                _mockDatabaseUserAccountsList.Add(sampleUserAccount);
                await SaveAccountListAsync();
            }
        }
    }
    
  • Ora che il metodo InitializeSampleUserAccountsAsync esiste rimuovendo il commento dalla chiamata al metodo nel metodo LoadAccountListAsync .

    private async Task LoadAccountListAsync()
    {
        if (File.Exists(_userAccountListPath))
        {
            StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_userAccountListPath);
    
            string accountsXml = await FileIO.ReadTextAsync(accountsFile);
            DeserializeXmlToAccountList(accountsXml);
        }
    
        // If the UserAccountList does not contain the sampleUser Initialize the sample users
        // This is only needed as it in a Hand on Lab to demonstrate a user migrating
        // In the real world user accounts would just be in a database
        if (!_mockDatabaseUserAccountsList.Any(f = > f.Username.Equals("sampleUsername")))
                {
            //If the list is empty InitializeSampleUserAccountsAsync and return the list
            await InitializeSampleUserAccountsAsync();
        }
    }
    
  • L'elenco degli account utente nel negozio fittizio ora può essere salvato e caricato. Altre parti dell'applicazione dovranno avere accesso a questo elenco, quindi saranno necessari alcuni metodi per recuperare questi dati. Sotto il metodo InitializeSampleUserAccountsAsync aggiungere i metodi seguenti per ottenere i dati. Consentono di ottenere un ID utente, un singolo utente, un elenco di utenti per un dispositivo Windows Hello specifico e ottenere anche la chiave pubblica per l'utente in un dispositivo specifico.

    public Guid GetUserId(string username)
    {
        if (_mockDatabaseUserAccountsList.Any())
        {
            UserAccount account = _mockDatabaseUserAccountsList.FirstOrDefault(f => f.Username.Equals(username));
            if (account != null)
            {
                return account.UserId;
            }
        }
        return Guid.Empty;
    }
    
    public UserAccount GetUserAccount(Guid userId)
    {
        return _mockDatabaseUserAccountsList.FirstOrDefault(f => f.UserId.Equals(userId));
    }
    
    public List<UserAccount> GetUserAccountsForDevice(Guid deviceId)
    {
        var usersForDevice = new List<UserAccount>();
    
        foreach (UserAccount account in _mockDatabaseUserAccountsList)
        {
            if (account.WindowsHelloDevices.Any(f => f.DeviceId.Equals(deviceId)))
            {
                usersForDevice.Add(account);
            }
        }
    
        return usersForDevice;
    }
    
    public byte[] GetPublicKey(Guid userId, Guid deviceId)
    {
        UserAccount account = _mockDatabaseUserAccountsList.FirstOrDefault(f => f.UserId.Equals(userId));
        if (account != null)
        {
            if (account.WindowsHelloDevices.Any())
            {
                return account.WindowsHelloDevices.FirstOrDefault(p => p.DeviceId.Equals(deviceId)).PublicKey;
            }
        }
        return null;
    }
    
  • I metodi successivi da implementare gestiranno operazioni semplici per aggiungere un account, rimuovere un account e rimuovere anche un dispositivo. La rimozione di un dispositivo è necessaria perché Windows Hello è specifico del dispositivo. Per ogni dispositivo a cui accedi, Windows Hello creerà una nuova coppia di chiavi pubblica e privata. È come avere una password diversa per ogni dispositivo che si accede, l'unica cosa è che non è necessario ricordare tutte queste password; il server esegue questa operazione. Aggiungere i metodi seguenti nella MockStore.cs.

    public async Task<UserAccount> AddAccountAsync(string username)
    {
        UserAccount newAccount = null;
        try
        {
            newAccount = new UserAccount()
            {
                UserId = Guid.NewGuid(),
                Username = username,
            };
    
            _mockDatabaseUserAccountsList.Add(newAccount);
            await SaveAccountListAsync();
        }
        catch (Exception)
        {
            throw;
        }
        return newAccount;
    }
    
    public async Task<bool> RemoveAccountAsync(Guid userId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        if (userAccount != null)
        {
            _mockDatabaseUserAccountsList.Remove(userAccount);
            await SaveAccountListAsync();
            return true;
        }
        return false;
    }
    
    public async Task<bool> RemoveDeviceAsync(Guid userId, Guid deviceId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        WindowsHelloDevice deviceToRemove = null;
        if (userAccount != null)
        {
            foreach (WindowsHelloDevice device in userAccount.WindowsHelloDevices)
            {
                if (device.DeviceId.Equals(deviceId))
                {
                    deviceToRemove = device;
                    break;
                }
            }
        }
    
        if (deviceToRemove != null)
        {
            //Remove the WindowsHelloDevice
            userAccount.WindowsHelloDevices.Remove(deviceToRemove);
            await SaveAccountListAsync();
        }
    
        return true;
    }
    
  • Nella classe MockStore aggiungere un metodo che aggiungerà le informazioni correlate a Windows Hello a un UserAccount esistente. Questo metodo verrà chiamato "WindowsHelloUpdateDetailsAsync" e accetta i parametri per identificare l'utente e i dettagli di Windows Hello. KeyAttestationResult è stato commentato durante la creazione di un WindowsHelloDevice, in un'applicazione reale che è necessario.

    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    public async Task WindowsHelloUpdateDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        UserAccount existingUserAccount = GetUserAccount(userId);
        if (existingUserAccount != null)
        {
            if (!existingUserAccount.WindowsHelloDevices.Any(f => f.DeviceId.Equals(deviceId)))
            {
                existingUserAccount.WindowsHelloDevices.Add(new WindowsHelloDevice()
                {
                    DeviceId = deviceId,
                    PublicKey = publicKey,
                    // KeyAttestationResult = keyAttestationResult
                });
            }
        }
        await SaveAccountListAsync();
    }
    
  • La classe MockStore è ora completa, perché rappresenta il database che deve essere considerato privato. Per accedere a MockStore, è necessaria una classe AuthService per modificare i dati del database. Nella cartella AuthService creare una nuova classe denominata "AuthService.cs". Modificare l'ambito della classe in public e aggiungere un modello di istanza singleton per assicurarsi che venga creata una sola istanza.

    namespace WindowsHelloLogin.AuthService
    {
        public class AuthService
        {
            // Singleton instance of the AuthService
            // The AuthService is a mock of what a real world server and service implementation would be
            private static AuthService _instance;
            public static AuthService Instance
            {
                get
                {
                    if (null == _instance)
                    {
                        _instance = new AuthService();
                    }
                    return _instance;
                }
            }
    
            private AuthService()
            { }
        }
    }
    
  • La classe AuthService deve creare un'istanza della classe MockStore e fornire l'accesso alle proprietà dell'oggetto MockStore .

    namespace WindowsHelloLogin.AuthService
    {
        public class AuthService
        {
            //Singleton instance of the AuthService
            //The AuthService is a mock of what a real world server and database implementation would be
            private static AuthService _instance;
            public static AuthService Instance
            {
                get
                {
                    if (null == _instance)
                    {
                        _instance = new AuthService();
                    }
                    return _instance;
                }
            }
    
            private AuthService()
            { }
    
            private MockStore _mockStore = new();
    
            public Guid GetUserId(string username)
            {
                return _mockStore.GetUserId(username);
            }
    
            public UserAccount GetUserAccount(Guid userId)
            {
                return _mockStore.GetUserAccount(userId);
            }
    
            public List<UserAccount> GetUserAccountsForDevice(Guid deviceId)
            {
                return _mockStore.GetUserAccountsForDevice(deviceId);
            }
        }
    }
    
  • Sono necessari metodi nella classe AuthService per accedere ai metodi di aggiunta, rimozione e aggiornamento dei dettagli di Windows Hello nell'oggetto MockStore . Alla fine della definizione della classe AuthService aggiungere i metodi seguenti.

    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    public async Task RegisterAsync(string username)
    {
        await _mockStore.AddAccountAsync(username);
    }
    
    public async Task<bool> WindowsHelloRemoveUserAsync(Guid userId)
    {
        return await _mockStore.RemoveAccountAsync(userId);
    }
    
    public async Task<bool> WindowsHelloRemoveDeviceAsync(Guid userId, Guid deviceId)
    {
        return await _mockStore.RemoveDeviceAsync(userId, deviceId);
    }
    
    public async Task WindowsHelloUpdateDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        await _mockStore.WindowsHelloUpdateDetailsAsync(userId, deviceId, publicKey, keyAttestationResult);
    }
    
  • La classe AuthService deve fornire un metodo per convalidare le credenziali. Questo metodo richiederà un nome utente e una password e assicurerà che l'account esista e che la password sia valida. Un sistema esistente avrebbe un metodo equivalente a questo che verifica che l'utente sia autorizzato. Aggiungere il metodo ValidateCredentials seguente al file AuthService.cs.

    public bool ValidateCredentials(string username, string password)
    {
        if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
        {
            // This would be used for existing accounts migrating to use Windows Hello
            Guid userId = GetUserId(username);
            if (userId != Guid.Empty)
            {
                UserAccount account = GetUserAccount(userId);
                if (account != null)
                {
                    if (string.Equals(password, account.Password))
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
  • La classe AuthService richiede un metodo di richiesta di verifica che restituisca una richiesta di verifica al client per verificare se l'utente è l'utente che dichiara di essere. È quindi necessario un altro metodo nella classe AuthService per ricevere la richiesta di verifica firmata dal client. Per questo lab pratico, il metodo di come determinare se la richiesta firmata è stata completata è stata lasciata incompleta. Ogni implementazione di Windows Hello in un sistema di autenticazione esistente sarà leggermente diversa. La chiave pubblica archiviata sul server deve corrispondere al risultato restituito dal client al server. Aggiungi questi due metodi ad AuthService.cs.

    using Windows.Security.Cryptography;
    using Windows.Storage.Streams;
    
    public IBuffer WindowsHelloRequestChallenge()
    {
        return CryptographicBuffer.ConvertStringToBinary("ServerChallenge", BinaryStringEncoding.Utf8);
    }
    
    public bool SendServerSignedChallenge(Guid userId, Guid deviceId, byte[] signedChallenge)
    {
        // Depending on your company polices and procedures this step will be different
        // It is at this point you will need to validate the signedChallenge that is sent back from the client.
        // Validation is used to ensure the correct user is trying to access this account. 
        // The validation process will use the signedChallenge and the stored PublicKey 
        // for the username and the specific device signin is called from.
        // Based on the validation result you will return a bool value to allow access to continue or to block the account.
    
        // For this sample validation will not happen as a best practice solution does not apply and will need to 
           // be configured for each company.
        // Simply just return true.
    
        // You could get the User's Public Key with something similar to the following:
        byte[] userPublicKey = _mockStore.GetPublicKey(userId, deviceId);
        return true;
    }
    

Esercizio 2: Logica lato client

In questo esercizio si modificheranno le visualizzazioni lato client e le classi helper dal primo lab per usare la classe AuthService . Nel mondo reale, AuthService sarebbe il server di autenticazione e sarebbe necessario usare l'API Web per inviare e ricevere dati dal server. Per questo lab pratico, il client e il server sono entrambi locali per mantenere le cose semplici. L'obiettivo è imparare a utilizzare le API di Windows Hello.

  • Nella MainPage.xaml.cs è possibile rimuovere la chiamata al metodo AccountHelper.LoadAccountListAsync nel metodo caricato quando la classe AuthService crea un'istanza di MockStore per caricare l'elenco degli account. Il Loaded metodo dovrebbe ora essere simile al frammento di codice seguente. Si noti che la definizione del metodo asincrono viene rimossa perché non viene attesa alcuna operazione.

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Aggiornare l'interfaccia della pagina di accesso per richiedere l'immissione di una password. Questo laboratorio pratico dimostra come è possibile eseguire la migrazione di un sistema esistente per utilizzare Windows Hello e come gli account esistenti avranno un nome utente e una password. Aggiorna anche la spiegazione nella parte inferiore del codice XAML per includere la password predefinita. Aggiornare il codice XAML seguente in Login.xaml.

    <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 credentials below" Margin="0,0,0,20"
                   TextWrapping="Wrap" Width="300"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
          <!-- Username Input -->
          <TextBlock x:Name="UserNameTextBlock" Text="Username: "
                     FontSize="20" Margin="4" Width="100"/>
          <TextBox x:Name="UsernameTextBox" PlaceholderText="sampleUsername" Width="200" Margin="4"/>
        </StackPanel>
    
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
          <!-- Password Input -->
          <TextBlock x:Name="PasswordTextBlock" Text="Password: "
                     FontSize="20" Margin="4" Width="100"/>
          <PasswordBox x:Name="PasswordBox" PlaceholderText="samplePassword" Width="200" Margin="4"/>
        </StackPanel>
    
        <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' and default password 'samplePassword'"/>
      </StackPanel>
    </Grid>
    
  • Nel file code-behind per la classe Login sarà necessario modificare la Account variabile privata all'inizio della classe in modo che sia .UserAccount Modificare l'evento OnNavigateTo per eseguire il cast del tipo come .UserAccount Sarà necessaria anche l'istruzione using seguente.

    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private UserAccount _account;
            private bool _isExistingAccount;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                //Check Windows Hello is setup 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 = (UserAccount)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        await SignInWindowsHelloAsync();
                    }
                }
            }
    
            private async void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                await SignInWindowsHelloAsync();
            }
        }
    }
    
  • Poiché la pagina Login usa un UserAccount oggetto invece dell'oggetto precedente Account , il WindowsHelloHelper.cs dovrà essere aggiornato per utilizzare un UserAccount oggetto come tipo di parametro per alcuni metodi. Sarà necessario modificare i parametri seguenti per i metodi CreateWindowsHelloKeyAsync, RemoveWindowsHelloAccountAsync e GetWindowsHelloAuthenticationMessageAsync . Poiché la UserAccount classe ha per Guid un UserId, si inizierà a usare l'ID in più posizioni per essere più precisi.

    public static async Task<bool> CreateWindowsHelloKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    
        return true;
    }
    
    public static async void RemoveWindowsHelloAccountAsync(UserAccount account)
    {
    
    }
    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(UserAccount 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.UserId, account.Username))
            {
                //If the Windows Hello Key was again successfully created, Windows Hello has just been reset.
                //Now that the Windows 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;
    }
    
  • Il metodo SignInWindowsHelloAsync in Login.xaml.cs file deve essere aggiornato per usare AuthService anziché AccountHelper. La convalida delle credenziali verrà eseguita tramite AuthService. Per questo lab pratico, l'unico account configurato è "sampleUsername". Questo account viene creato nel metodo InitializeSampleUserAccountsAsync in MockStore.cs. Aggiornare il metodo SignInWindowsHelloAsync in Login.xaml.cs ora per riflettere il frammento di codice seguente.

    private async Task SignInWindowsHelloAsync()
    {
        if (_isExistingAccount)
        {
            if (await WindowsHelloHelper.GetWindowsHelloAuthenticationMessageAsync(_account))
            {
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else if (AuthService.AuthService.Instance.ValidateCredentials(UsernameTextBox.Text, PasswordBox.Password))
        {
            Guid userId = AuthService.AuthService.Instance.GetUserId(UsernameTextBox.Text);
    
            if (userId != Guid.Empty)
            {
                //Now that the account exists on server try and create the necessary details and add them to the account
                if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(userId, UsernameTextBox.Text))
                {
                    Debug.WriteLine("Successfully signed in with Windows Hello!");
                    //Navigate to the Welcome Screen. 
                    _account = AuthService.AuthService.Instance.GetUserAccount(userId);
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    //The Windows Hello account creation failed.
                    //Remove the account from the server as the details were not configured
                    await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Poiché Windows Hello creerà una coppia di chiavi pubblica e privata diversa per ogni account in ogni dispositivo, la pagina iniziale dovrà visualizzare un elenco di dispositivi registrati per l'account connesso e consentire a ognuno di essi di essere dimenticati. In Welcome.xaml aggiungere il codice XAML seguente sotto .ForgetButton Ciò implementerà un pulsante per dimenticare il dispositivo, un'area di testo di errore e un elenco per visualizzare tutti i dispositivi.

    <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"/>
    
        <Button x:Name="ForgetDeviceButton" Content="Forget Device" Click="Button_Forget_Device_Click"
                Foreground="White"
                Background="Gray"
                Margin="0,40,0,20"
                HorizontalAlignment="Center"/>
    
        <TextBlock x:Name="ForgetDeviceErrorTextBlock" Text="Select a device first"
                   TextWrapping="Wrap" Width="300" Foreground="Red"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16" Visibility="Collapsed"/>
    
        <ListView x:Name="UserListView" MaxHeight="500" MinWidth="350" Width="350" HorizontalAlignment="Center">
          <ListView.ItemTemplate>
            <DataTemplate>
              <Grid Background="Gray" Height="50" Width="350" HorizontalAlignment="Center" VerticalAlignment="Stretch" >
                <TextBlock Text="{Binding DeviceId}" HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center"
                           Foreground="White"/>
              </Grid>
            </DataTemplate>
          </ListView.ItemTemplate>
        </ListView>
      </StackPanel>
    </Grid>
    
  • Nel file Welcome.xaml.cs è necessario modificare la variabile privata Account all'inizio della classe in modo che sia una variabile privata UserAccount . Aggiornare quindi il OnNavigatedTo metodo per usare AuthService e recuperare le informazioni per l'account corrente. Quando si dispone delle informazioni sull'account, è possibile impostare l'oggetto ItemsSource dell'elenco per visualizzare i dispositivi. Sarà necessario aggiungere un riferimento allo spazio dei nomi AuthService .

    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Welcome : Page
        {
            private UserAccount _activeAccount;
    
            public Welcome()
            {
                InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                _activeAccount = (UserAccount)e.Parameter;
                if (_activeAccount != null)
                {
                    UserAccount account = AuthService.AuthService.Instance.GetUserAccount(_activeAccount.UserId);
                    if (account != null)
                    {
                        UserListView.ItemsSource = account.WindowsHelloDevices;
                        UserNameText.Text = account.Username;
                    }
                }
            }
        }
    }
    
  • Poiché si usa AuthService quando si rimuove un account, è possibile rimuovere il riferimento a AccountHelper nel Button_Forget_User_Click metodo . Il metodo dovrebbe ora apparire come di seguito.

    private async void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        //Remove it from Windows Hello
        await WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
        //Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Il metodo WindowsHelloHelper non usa AuthService per rimuovere l'account. È necessario effettuare una chiamata a AuthService e passare l'id utente.

    public static async void RemoveWindowsHelloAccountAsync(UserAccount 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
            await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(account.UserId);
        }
    
        //Then delete the account from the machines list of Windows Hello Accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • Prima di poter completare l'implementazione della pagina iniziale , è necessario creare un metodo in WindowsHelloHelper.cs che consentirà la rimozione di un dispositivo. Creare un nuovo metodo che chiamerà WindowsHelloRemoveDeviceAsync in AuthService.

    public static async Task RemoveWindowsHelloDeviceAsync(UserAccount account, Guid deviceId)
    {
        await AuthService.AuthService.Instance.WindowsHelloRemoveDeviceAsync(account.UserId, deviceId);
    }
    
  • In Welcome.xaml.cs implementare il gestore eventi Button_Forget_Device_Click . Questo userà il dispositivo selezionato dall'elenco dei dispositivi e userà l'helper Windows Hello per chiamare il dispositivo di rimozione. Ricordarsi di rendere asincrono il gestore eventi.

    private async void Button_Forget_Device_Click(object sender, RoutedEventArgs e)
    {
        WindowsHelloDevice selectedDevice = UserListView.SelectedItem as WindowsHelloDevice;
        if (selectedDevice != null)
        {
            //Remove it from Windows Hello
            await WindowsHelloHelper.RemoveWindowsHelloDeviceAsync(_activeAccount, selectedDevice.DeviceId);
    
            Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
            if (!UserListView.Items.Any())
            {
                //Navigate back to UserSelection page.
                Frame.Navigate(typeof(UserSelection));
            }
        }
        else
        {
            ForgetDeviceErrorTextBlock.Visibility = Visibility.Visible;
        }
    }
    
  • La pagina successiva che aggiornerai è la pagina UserSelection . La pagina UserSelection dovrà usare AuthService per recuperare tutti gli account utente per il dispositivo corrente. Attualmente, non è possibile ottenere un ID dispositivo da passare a AuthService in modo che possa restituire gli account utente per tale dispositivo. Nella cartella Utils creare una nuova classe denominata "Helpers.cs". Modificare l'ambito della classe in modo che sia statico pubblico e quindi aggiungere il metodo seguente che consentirà di recuperare l'ID dispositivo corrente.

    using System;
    using Windows.Security.ExchangeActiveSyncProvisioning;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class Helpers
        {
            public static Guid GetDeviceId()
            {
                //Get the Device ID to pass to the server
                var deviceInformation = new EasClientDeviceInformation();
                return deviceInformation.Id;
            }
        }
    }
    
  • Nella classe della pagina UserSelection è necessario modificare solo il code-behind, non l'interfaccia utente. In UserSelection.xaml.cs aggiornare il metodo UserSelection_Loaded e il metodo UserSelectionChanged per usare la classe anziché la UserAccount Account classe . Sarà anche necessario ottenere tutti gli utenti per questo dispositivo tramite AuthService.

    using System.Linq;
    using WindowsHelloLogin.AuthService;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class UserSelection : Page
        {
            public UserSelection()
            {
                InitializeComponent();
                Loaded += UserSelection_Loaded;
            }
    
            private void UserSelection_Loaded(object sender, RoutedEventArgs e)
            {
                List<UserAccount> accounts = AuthService.AuthService.Instance.GetUserAccountsForDevice(Helpers.GetDeviceId());
    
                if (accounts.Any())
                {
                    UserListView.ItemsSource = accounts;
                    UserListView.SelectionChanged += UserSelectionChanged;
                }
                else
                {
                    //If there are no accounts navigate to the Login page
                    Frame.Navigate(typeof(Login));
                }
            }
    
            /// <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)
                {
                    UserAccount account = (UserAccount)((ListView)sender).SelectedValue;
                    if (account != null)
                    {
                        Debug.WriteLine($"Account {account.Username} selected!");
                    }
                    Frame.Navigate(typeof(Login), account);
                }
            }
        }
    }
    
  • La pagina WindowsHelloRegister deve avere aggiornato il file code-behind. L'interfaccia utente non richiede alcuna modifica. In WindowsHelloRegister.xaml.cs rimuovere la variabile privata Account all'inizio della classe, perché non è più necessaria. Aggiornare il gestore eventi RegisterButton_Click_Async per usare AuthService. Questo metodo creerà un nuovo UserAccount e quindi tenterà di aggiornarne i dettagli. Se Windows Hello non riesce a creare una chiave, l'account verrà rimosso perché il processo di registrazione non è riuscito.

    private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
    {
        ErrorMessage.Text = "";
    
        //Validate entered credentials are acceptable
        if (!string.IsNullOrEmpty(UsernameTextBox.Text))
        {
            //Register an Account on the AuthService so that we can get back a userId
            await AuthService.AuthService.Instance.RegisterAsync(UsernameTextBox.Text);
            Guid userId = AuthService.AuthService.Instance.GetUserId(UsernameTextBox.Text);
    
            if (userId != Guid.Empty)
            {
                //Now that the account exists on server try and create the necessary details and add them to the account
                if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(userId, UsernameTextBox.Text))
                {
                    //Navigate to the Welcome Screen. 
                    Frame.Navigate(typeof(Welcome), AuthService.AuthService.Instance.GetUserAccount(userId));
                }
                else
                {
                    //The Windows Hello account creation failed.
                    //Remove the account from the server as the details were not configured
                    await AuthService.AuthService.Instance.WindowsHelloRemoveUserAsync(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Please enter a username";
        }
    }
    
  • Compilare ed eseguire l'applicazione. Accedere all'account utente di esempio con le credenziali "sampleUsername" e "samplePassword". Nella schermata iniziale è possibile notare che il pulsante Dimentica dispositivi è visualizzato, ma non sono presenti dispositivi. Quando si crea o si esegue la migrazione di un utente per l'uso con Windows Hello, le informazioni sull'account non vengono sottoposte a push in AuthService.

    Screenshot della schermata di accesso di Windows Hello

    Screenshot del completamento dell'accesso a Windows Hello

  • Per ottenere le informazioni sull'account Windows Hello per AuthService, è necessario aggiornare il WindowsHelloHelper.cs. Nel metodo CreateWindowsHelloKeyAsync, anziché restituire true solo nel caso in cui l'operazione sia riuscita, dovrai chiamare un nuovo metodo che tenterà di ottenere KeyAttestation. Anche se questo lab pratico non registra queste informazioni in AuthService, si apprenderà come ottenere queste informazioni sul lato client. Aggiornare il metodo CreateWindowsHelloKeyAsync come segue:

    public static async Task<bool> CreateWindowsHelloKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    
        switch (keyCreationResult.Status)
        {
            case KeyCredentialStatus.Success:
                Debug.WriteLine("Successfully made key");
                await GetKeyAttestationAsync(userId, keyCreationResult);
                return true;
            case KeyCredentialStatus.UserCanceled:
                Debug.WriteLine("User cancelled sign-in process.");
                break;
            case KeyCredentialStatus.NotFound:
                // User needs to setup Windows Hello
                Debug.WriteLine($"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • Creare un metodo GetKeyAttestationAsync in WindowsHelloHelper.cs. Questo metodo mostrerà come ottenere tutte le informazioni necessarie che possono essere fornite da Windows Hello per ciascun account su un dispositivo specifico.

    using Windows.Storage.Streams;
    
    private static async Task GetKeyAttestationAsync(Guid userId, KeyCredentialRetrievalResult keyCreationResult)
    {
        KeyCredential userKey = keyCreationResult.Credential;
        IBuffer publicKey = userKey.RetrievePublicKey();
        KeyCredentialAttestationResult keyAttestationResult = await userKey.GetAttestationAsync();
        IBuffer keyAttestation = null;
        IBuffer certificateChain = null;
        bool keyAttestationIncluded = false;
        bool keyAttestationCanBeRetrievedLater = false;
        KeyCredentialAttestationStatus keyAttestationRetryType = 0;
    
        if (keyAttestationResult.Status == KeyCredentialAttestationStatus.Success)
        {
            keyAttestationIncluded = true;
            keyAttestation = keyAttestationResult.AttestationBuffer;
            certificateChain = keyAttestationResult.CertificateChainBuffer;
            Debug.WriteLine("Successfully made key and attestation");
        }
        else if (keyAttestationResult.Status == KeyCredentialAttestationStatus.TemporaryFailure)
        {
            keyAttestationRetryType = KeyCredentialAttestationStatus.TemporaryFailure;
            keyAttestationCanBeRetrievedLater = true;
            Debug.WriteLine("Successfully made key but not attestation");
        }
        else if (keyAttestationResult.Status == KeyCredentialAttestationStatus.NotSupported)
        {
            keyAttestationRetryType = KeyCredentialAttestationStatus.NotSupported;
            keyAttestationCanBeRetrievedLater = false;
            Debug.WriteLine("Key created, but key attestation not supported");
        }
    
        Guid deviceId = Helpers.GetDeviceId();
    
        //Update the Windows Hello details with the information we have just fetched above.
        //await UpdateWindowsHelloDetailsAsync(userId, deviceId, publicKey.ToArray(), keyAttestationResult);
    }
    
  • Potresti aver notato nel metodo GetKeyAttestationAsync che hai appena aggiunto l'ultima riga è stata commentata. Questa ultima riga sarà un nuovo metodo creato che invierà tutte le informazioni di Windows Hello a AuthService. Nel mondo reale, è necessario inviarlo a un server effettivo tramite un'API Web.

    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Threading.Tasks;
    
    public static async Task<bool> UpdateWindowsHelloDetailsAsync(Guid userId, Guid deviceId, byte[] publicKey, KeyCredentialAttestationResult keyAttestationResult)
    {
        //In the real world, you would use an API to add Windows Hello signing info to server for the signed in account.
        //For this tutorial, we do not implement a Web API for our server and simply mock the server locally.
        //The CreateWindowsHelloKey method handles adding the Windows Hello account locally to the device using the KeyCredential Manager
    
        //Using the userId the existing account should be found and updated.
        await AuthService.AuthService.Instance.WindowsHelloUpdateDetailsAsync(userId, deviceId, publicKey, keyAttestationResult);
        return true;
    }
    
  • Rimuovere il commento dall'ultima riga nel metodo GetKeyAttestationAsync in modo che le informazioni di Windows Hello vengano inviate a AuthService.

  • Compila ed esegui l'applicazione e accedi con le credenziali predefinite come prima. Nella pagina iniziale si noterà ora che l'ID dispositivo è visualizzato. Se hai effettuato l'accesso su un altro dispositivo, verrebbe visualizzato anche qui (se disponi di un servizio di autenticazione ospitato nel cloud). Per questo lab pratico, viene visualizzato l'ID dispositivo effettivo. In un'implementazione reale, si vuole visualizzare un nome descrittivo che una persona può comprendere e usare per identificare ogni dispositivo.

    Screenshot dell'account di accesso riuscito di Windows Hello che mostra l'ID dispositivo

  • Per completare questo lab pratico, è necessaria una richiesta e una richiesta di verifica per l'utente quando selezionano dalla pagina di selezione dell'utente e accedi di nuovo. AuthService include due metodi creati per richiedere una richiesta di verifica, una che usa una richiesta di verifica firmata. In WindowsHelloHelper.cs creare un nuovo metodo denominato RequestSignAsync. Verrà richiesta una richiesta di verifica da parte di AuthService, firmando localmente la richiesta di verifica usando un'API Windows Hello e inviando la richiesta firmata a AuthService. In questo lab pratico, AuthService riceverà la richiesta firmata e restituirà true. In un'implementazione effettiva, è necessario implementare un meccanismo di verifica per determinare se la richiesta di verifica è stata firmata dall'utente corretto nel dispositivo corretto. Aggiungere il metodo seguente alla WindowsHelloHelper.cs

    private static async Task<bool> RequestSignAsync(Guid userId, KeyCredentialRetrievalResult openKeyResult)
    {
        // Calling userKey.RequestSignAsync() prompts the uses to enter the PIN or use Biometrics (Windows Hello).
        // The app would use the private key from the user account to sign the sign-in request (challenge)
        // The client would then send it back to the server and await the servers response.
        IBuffer challengeMessage = AuthService.AuthService.Instance.WindowsHelloRequestChallenge();
        KeyCredential userKey = openKeyResult.Credential;
        KeyCredentialOperationResult signResult = await userKey.RequestSignAsync(challengeMessage);
    
        if (signResult.Status == KeyCredentialStatus.Success)
        {
            // If the challenge from the server is signed successfully
            // send the signed challenge back to the server and await the servers response
            return AuthService.AuthService.Instance.SendServerSignedChallenge(
                userId, Helpers.GetDeviceId(), signResult.Result.ToArray());
        }
        else if (signResult.Status == KeyCredentialStatus.UserCanceled)
        {
            // User cancelled the Windows Hello PIN entry.
        }
        else if (signResult.Status == KeyCredentialStatus.NotFound)
        {
            // Must recreate Windows Hello key
        }
        else if (signResult.Status == KeyCredentialStatus.SecurityDeviceLocked)
        {
            // Can't use Windows Hello right now, remember that hardware failed and suggest restart
        }
        else if (signResult.Status == KeyCredentialStatus.UnknownError)
        {
            // Can't use Windows Hello right now, try again later
        }
    
        return false;
    }
    
  • Nella classe WindowsHelloHelper chiamare il metodo RequestSignAsync dal metodo GetWindowsHelloAuthenticationMessageAsync.

    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(UserAccount 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.
    
            return await RequestSignAsync(account.UserId, openKeyResult);
        }
        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.UserId, account.Username))
            {
                //If the Windows Hello Key was again successfully created, Windows Hello has just been reset.
                //Now that the Windows 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;
    }
    
  • In questo esercizio è stata aggiornata l'applicazione lato client per l'uso di AuthService. In questo modo, è stato possibile eliminare la necessità della classe Account e della classe AccountHelper . Eliminare la classe Account , la cartella Models e la classe AccountHelper nella cartella Utils . Sarà necessario rimuovere tutto il WindowsHelloLogin.Models riferimento allo spazio dei nomi in tutta l'applicazione prima che la soluzione venga compilata correttamente.

  • Crea ed esegui l'applicazione e divertiti a utilizzare Windows Hello con il servizio e il database fittizi.

In questo lab pratico si è appreso come usare le API di Windows Hello per sostituire la necessità di password quando si usa l'autenticazione da un computer Windows. Se consideri quanta energia viene spesa dalle persone che mantengono le password e supportano le password perse nei sistemi esistenti, dovresti vedere il vantaggio del passaggio a questo nuovo sistema di autenticazione Windows Hello.

È stato lasciato come esercizio per informazioni dettagliate su come implementare l'autenticazione sul lato servizio e server. È previsto che la maggior parte degli sviluppatori disponga di sistemi esistenti che dovranno essere migrati per iniziare a usare Windows Hello. I dettagli di ognuno di questi sistemi saranno diversi.