Создание службы входа Windows HelloCreate a Windows Hello login service

Это вторая часть полного пошагового руководства по использованию Windows Hello в качестве альтернативы традиционным системам проверки подлинности с помощью имени пользователя и пароля в приложениях универсальной платформы Windows 10 (UWP).This is Part 2 of a complete walkthrough on how to use Windows Hello as an alternative to traditional username and password authentication systems in Windows 10 UWP (Universal Windows platform) apps. Эта статья является продолжением первой части Приложение для входа с использованием Windows Hello и описывает расширение функций, чтобы продемонстрировать интеграцию Windows Hello в существующие приложения.This article picks up where Part 1, Windows Hello login app, left off and extends the functionality to demonstrate how you can integrate Windows Hello into your existing application.

Для создания этого проекта необходим опыт работы с C# и XAML.In order to build this project, you'll need some experience with C#, and XAML. Вам также потребуется использовать Visual Studio 2015 (Community Edition или более старший выпуск) на компьютере под управлением Windows 10.You'll also need to be using Visual Studio 2015 (Community Edition or greater) on a Windows 10 machine.

Упражнение 1. Логика на стороне сервераExercise 1: Server Side Logic

В этом упражнении вы будете использовать приложение для Windows Hello, созданное на первом практикуме, и создадите локальные сервер и базу данных.In this exercise you will be starting with the Windows Hello application built in the first lab and creating a local mock server and database. В этом практическом занятии вы узнаете, как интегрировать Windows Hello в существующую систему.This hands on lab is designed to teach how Windows Hello could be integrated into an existing system. С помощью макетного сервера и базы данных устраняется необходимость в настройке многих несущественных параметров.By using a mock server and mock database a lot of unrelated setup is eliminated. В ваших собственных приложениях потребуется заменить макетные объекты реальными службами и базами данных.In your own applications you will need to replace the mock objects with the real services and databases.

  • Откройте решение PassportLogin из первого практического занятия по Microsoft Passport.To begin, open up the PassportLogin solution from the first Passport Hands On Lab.

  • Начнем с реализации имитации сервера и базы данных.You will start by implementing the mock server and mock database. Создайте новую папку с именем "AuthService".Create a new folder called "AuthService". Для этого щелкните правой кнопкой мыши решение «PassportLogin (универсальные приложения для Windows)» в обозревателе решений и выберите «Добавить > Новая папка».In solution explorer right click on the solution "PassportLogin (Universal Windows)" and select Add > New Folder.

  • Создайте классы UserAccount и PassportDevices, которые будут использоваться в качестве моделей данных, сохраняемых в макетной базе данных.Create UserAccount and PassportDevices classes that will act as models for data to be saved in the mock database. UserAccount будет аналогичен модели пользователя, реализованной на традиционном сервере проверки подлинности.The UserAccount will be similar to the user model implemented on a traditional authentication server. Щелкните правой кнопкой мыши папку AuthService и добавьте класс "UserAccount.cs".Right click on the AuthService folder and add a new class called "UserAccount.cs."

    Создание папки авторизации Windows Hello

    Создание класса авторизации Windows Hello

  • Измените определение класса на общий (public), а затем добавьте следующие открытые свойства.Change the class definition to be public and then add the following public properties. Вам потребуется следующая справочная информация.You will need the following reference.

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

    Возможно, вы заметили закомментированный список PassportDevices.You may have noticed the commented out list of PassportDevices. Это изменение следует внести в существующую модель пользователя в текущей реализации.This is a modification you will need to make to an existing user model in your current implementation. Список PassportDevices будет содержать deviceID, открытый ключ, созданный в Windows Hello, и класс KeyCredentialAttestationResult.The list of PassportDevices will contain a deviceID, the public key made from Windows Hello, and a KeyCredentialAttestationResult. В этом практическом занятии вам потребуется реализовать keyAttestationResult, поскольку аттестация ключа предоставляется Windows Hello только на устройствах с микросхемой доверенного платформенного модуля (TPM).For this hands on lab you will need to implement the keyAttestationResult as they are only provided by Windows Hello on devices that have a TPM (Trusted Platform Modules) chip. Класс KeyCredentialAttestationResult представляет собой комбинацию нескольких свойств и должен быть разделен для сохранения свойств в базе данных и их загрузки из базы данных.The KeyCredentialAttestationResult is a combination of multiple properties and would need to be split in order to save and load them with a database.

  • Создайте новый класс под названием "PassportDevice.cs" в папке AuthService.Create a new class in the AuthService folder called "PassportDevice.cs". Это модель для устройств Windows Hello, упомянутая ранее.This is the model for the Windows Hello devices as discussed above. Измените определение класса на общий (public) и добавьте следующие свойства.Change the class definition to be public and add the following properties.

    namespace PassportLogin.AuthService
    {
        public class PassportDevice
        {
            // 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; }
        }
    }
    
  • Вернитесь в UserAccount.cs и удалите комментарий для списка устройств Windows Hello.Return to in UserAccount.cs and uncomment the list of Windows Hello devices.

    using System.Collections.Generic;
    
    namespace PassportLogin.AuthService
    {
        public class UserAccount
        {
            [Key, Required]
            public Guid UserId { get; set; }
            [Required]
            public string Username { get; set; }
            public string Password { get; set; }
            public List<PassportDevice> PassportDevices = new List<PassportDevice>();
        }
    }
    
  • После создания моделей UserAccount и PassportDevice необходимо создать в AuthService еще один класс, который будет служить макетной базой данных.With the model for the UserAccount and the PassportDevice created, you need to create another new class in the AuthService that will act as the mock database. Это макетная база данных, которую вы будете использовать для сохранения и загрузки списка пользовательских учетных записей локально.As this is a mock database from where you will be saving and loading a list of user accounts locally. В реальных сценариях это была бы ваша реализация базы данных.In the real world this would be your database implementation. Создайте в AuthService новый класс "MockStore.cs".Create a new class in AuthService called "MockStore.cs". Измените определение класса на public.Change the class definition to public.

  • Так как макетное хранилище сохраняет и загружает список учетных записей пользователей локально, вы можете реализовать логику сохранения и загрузки списка, используя XmlSerializer.As the mock store will save and load a list of user accounts locally you can implement the logic to save and load that list using an XmlSerializer. Вам также необходимо запомнить имя файла и его расположение.You will also need to remember the filename and save location. Реализуйте в файле MockStore.cs следующие элементы.In MockStore.cs implement the following:

    using System.IO;
    using System.Linq;
    using System.Xml.Serialization;
    using Windows.Storage;

    namespace PassportLogin.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 void 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 void 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 InitializeSampleAccounts and return the list
                    //InitializeSampleUserAccounts();
                }
            }
     
            /// <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()
            {
                XmlSerializer xmlizer = new XmlSerializer(typeof(List<UserAccount>));
                StringWriter 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)
            {
                XmlSerializer 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
        }
    }
  • Возможно, вы заметили, что метод InitializeSampleUserAccounts в методе Load закомментирован. Необходимо создать этот метод в MockStore.cs.In the load method you may have noticed that an InitializeSampleUserAccounts method was commented out. You will need to create this method in the MockStore.cs. Он будет заполнять список учетных записей, чтобы можно было произвести вход.This method will populate the user accounts list so that a login can take place. В реальных сценариях база данных пользователей будет уже заполнена.In the real world the user database would already be populated. На этом шаге вы также создадите конструктор для инициализации списка пользователей и вызова загрузки.In this step you will also be creating a constructor that will initialise the user list and call load.

    namespace PassportLogin.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& lt; UserAccount & gt; ();
                LoadAccountListAsync();
            }
    
            private void InitializeSampleUserAccounts()
            {
                // 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
    
                UserAccount sampleUserAccount = new UserAccount()
                {
                    UserId = Guid.NewGuid(),
                    Username = "sampleUsername",
                    Password = "samplePassword",
                };
    
                // Add the sampleUserAccount to the _mockDatabase
                _mockDatabaseUserAccountsList.Add(sampleUserAccount);
                SaveAccountListAsync();
            }
        }
    }
    
  • Теперь, когда метод InitalizeSampleUserAccounts существует, удалите комментарий вызова этого метода в методе LoadAccountListAsync.Now that the InitalizeSampleUserAccounts method exists uncomment the method call in the LoadAccountListAsync method.

    private async void 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 InitializeSampleAccounts and return the list
            InitializeSampleUserAccounts();
        }
    }
    
  • Теперь можно использовать макетное хранилище для сохранения и загрузки списка учетных записей пользователей.The user accounts list in mock store can now be saved and loaded. Доступ к этому списку потребуется и другим частям приложения, поэтому необходимо реализовать методы для получения этих данных.Other parts of the application will need to have access to this list so there will need to be some methods to retrieve this data. Добавьте следующие методы Get в метод InitializeSampleUserAccounts.Underneath the InitializeSampleUserAccounts method, add the following get methods. Они позволят вам получить идентификатор пользователя, одиночного пользователя, список пользователей на определенном устройстве Windows Hello, а также открытый ключ для пользователя определенного устройства.They will allow you to get a userid, a single user, a list of users for a specific Windows Hello device, and also get the public key for the user on a specific device.

    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)
    {
        List<UserAccount> usersForDevice = new List<UserAccount>();
    
        foreach (UserAccount account in _mockDatabaseUserAccountsList)
        {
            if (account.PassportDevices.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.PassportDevices.Any())
            {
                return account.PassportDevices.FirstOrDefault(p => p.DeviceId.Equals(deviceId)).PublicKey;
            }
        }
        return null;
    }
    
  • Следующие методы, которые необходимо реализовать, обрабатывают простые операции добавления и удаления учетной записи, а также удаления устройства.The next methods to implement will handle simple operations to add account, remove account, and also remove device. Удаление устройства необходимо, так как Windows Hello учитывает конкретное устройство.Remove device is needed as Windows Hello is device specific. Windows Hello создает новую пару открытых и закрытых ключей для каждого устройства, на котором выполняется вход.For each device to which you log in, a new public and private key pair will be created by Windows Hello. Это похоже на использование для входа отдельного пароля для каждого устройства, но при этом вам не нужно запоминать все эти пароли, так как за вас это делает сервер.It is like having a different password for each device you sign in on, the only thing is you don’t need to remember all those passwords the server does. Добавьте в файл MockStore.cs следующие методы.Add the following methods into the MockStore.cs

    public UserAccount AddAccount(string username)
    {
        UserAccount newAccount = null;
        try
        {
            newAccount = new UserAccount()
            {
                UserId = Guid.NewGuid(),
                Username = username,
            };
    
            _mockDatabaseUserAccountsList.Add(newAccount);
            SaveAccountListAsync();
        }
        catch (Exception)
        {
            throw;
        }
        return newAccount;
    }
    
    public bool RemoveAccount(Guid userId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        if (userAccount != null)
        {
            _mockDatabaseUserAccountsList.Remove(userAccount);
            SaveAccountListAsync();
            return true;
        }
        return false;
    }
    
    public bool RemoveDevice(Guid userId, Guid deviceId)
    {
        UserAccount userAccount = GetUserAccount(userId);
        PassportDevice deviceToRemove = null;
        if (userAccount != null)
        {
            foreach (PassportDevice device in userAccount.PassportDevices)
            {
                if (device.DeviceId.Equals(deviceId))
                {
                    deviceToRemove = device;
                    break;
                }
            }
        }
    
        if (deviceToRemove != null)
        {
            //Remove the PassportDevice
            userAccount.PassportDevices.Remove(deviceToRemove);
            SaveAccountListAsync();
        }
    
        return true;
    }
    
  • В класс MockStore добавьте метод, который вносит данные Windows Hello в существующий UserAccount.In the MockStore class add a method that will add Windows Hello related information to an existing UserAccount. Этот метод будет называться PassportUpdateDetails и использовать параметры для идентификации пользователя и сведения Windows Hello.This method will be called PassportUpdateDetails and will take parameters to identify the user, and the Windows Hello details. KeyAttestationResult при создании PassportDevice был закомментирован. В реальном приложении вам потребуется его использовать.The KeyAttestationResult has been commented out when creating a PassportDevice, in a real world application you would require this.

    using Windows.Security.Credentials;
    
    public void PassportUpdateDetails(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        UserAccount existingUserAccount = GetUserAccount(userId);
        if (existingUserAccount != null)
        {
            if (!existingUserAccount.PassportDevices.Any(f => f.DeviceId.Equals(deviceId)))
            {
                existingUserAccount.PassportDevices.Add(new PassportDevice()
                {
                    DeviceId = deviceId,
                    PublicKey = publicKey,
                    // KeyAttestationResult = keyAttestationResult
                });
            }
        }
        SaveAccountListAsync();
    }
    
  • Класс MockStore готов, и поскольку он представляет базу данных, то должен считаться закрытым.The MockStore class is now complete, as this represents the database it should be considered private. Для доступа к MockStore и управления содержимым базы данных требуется класс AuthService.In order to access the MockStore an AuthService class is needed to manipulate the database data. Создайте новый класс под названием "AuthService.cs" в папке AuthService.In the AuthService folder create a new class called "AuthService.cs". Измените определение класса на public и добавьте одноэлементный шаблон экземпляра, чтобы всегда создавался только один экземпляр класса.Change the class definition to public and add a singleton instance pattern to make sure only one instance is ever created.

    namespace PassportLogin.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()
            { }
        }
    }
    
  • Класс AuthService должен создавать экземпляр класса MockStore и обеспечивать доступ к свойствам объекта MockStore.The AuthService class will need to create an instance of the MockStore class and provide access to the properties of the MockStore object.

    namespace PassportLogin.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 MockStore _mockStore = new MockStore();
    
            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);
            }
        }
    }
    
  • В классе AuthService вам потребуются методы для доступа к методам добавления, удаления и изменения сведений Microsoft Passport в объекте MockStore.You need methods in the AuthService class to access add, remove, and update passport details methods in the MockStore object. Добавьте в конец класса AuthService следующие методы.At the end of the AuthService class file add the following methods.

    using Windows.Security.Credentials;
    
    public void Register(string username)
    {
        _mockStore.AddAccount(username);
    }
    
    public bool PassportRemoveUser(Guid userId)
    {
        return _mockStore.RemoveAccount(userId);
    }
    
    public bool PassportRemoveDevice(Guid userId, Guid deviceId)
    {
        return _mockStore.RemoveDevice(userId, deviceId);
    }
    
    public void PassportUpdateDetails(Guid userId, Guid deviceId, byte[] publicKey, 
        KeyCredentialAttestationResult keyAttestationResult)
    {
        _mockStore.PassportUpdateDetails(userId, deviceId, publicKey, keyAttestationResult);
    }
    
  • Классу AuthService потребуется метод для проверки учетных данных.The AuthService class will need to provide a method to validate credentials. Этот метод принимает имя пользователя и пароль и проверяет существование учетной записи и действительность пароля.This method will take a username and password and make sure that account exists and the password is valid. В существующей системе должен быть аналогичный метод, проверяющий, авторизован ли пользователь.An existing system would have an equivalent method to this that checks the user is authorized. Добавьте в файл AuthService.cs следующий метод ValidateCredentials.Add the following ValidateCredentials to the AuthService.cs file.

    public bool ValidateCredentials(string username, string password)
    {
        if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
        {
            // This would be used for existing accounts migrating to use Passport
            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;
    }
    
  • Классу AuthService нужен метод запроса задачи, который возвращает задачу клиенту для проверки подлинности пользователя.The AuthService class needs a request challenge method that will return a challenge to the client to validate the user is who they claim to be. Также в классе AuthService необходим метод для получения подписанной задачи от клиента.Then a method is needed in the AuthService class to receive the signed challenge back from the client. В этом практическом занятии метод, определяющий завершение подписанной задачи, оставлен незаконченным.For this hands on lab the method of how you determine if the signed challenge has been completed has been left incomplete. Реализации Windows Hello в существующих системах проверки подлинности будут немного отличаться друг от друга.Every implementation of Windows Hello into an existing authentication system will be slightly different. Хранящийся на сервере открытый ключ должен совпадать с результатом, возвращаемым клиентом на сервер.The public key stored on the server needs to match with the result the client returned to the server. Добавьте эти два метода в файл AuthService.cs.Add these two methods to AuthService.cs.

    using Windows.Security.Cryptography;
    using Windows.Storage.Streams;
    
    public IBuffer PassportRequestChallenge()
    {
        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;
    }
    

Упражнение 2. Логика на стороне клиентаExercise 2: Client Side Logic

В этом упражнении вы будете вносить изменения в представления и вспомогательные классы на стороне клиента для использования класса AuthService.In this exercise you will be changing the client side views and helper classes from the first lab to use the AuthService class. В реальных сценариях AuthService будет сервером проверки подлинности, и вам потребуется использовать веб-API для отправки и получения данных с сервера.In the real world the AuthService would be the authentication server and you would need to use Web API’s to send and receive data from the server. В этом практическом занятии используются локальные клиент и сервер для упрощения разработки.For this hands on lab client and server are all local to keep things simple. Цель занятия — научиться использовать API Windows Hello.The objective is to learn how to use the Windows Hello APIs.

  • В файле MainPage.xaml.cs вызов метода AccountHelper.LoadAccountListAsync в методе loaded можно удалить, так как класс AuthService создает экземпляр MockStore, загружающий список учетных записей.In the MainPage.xaml.cs you can remove the AccountHelper.LoadAccountListAsync method call in the loaded method as the AuthService class creates an instance of the MockStore which loads the accounts list. Метод loaded теперь должен выглядеть следующим образом.The loaded method should now look like below. Обратите внимание, что определение асинхронного метода удалено, так как отсутствуют ожидающие задачи.Note the async method definition is removed as nothing is being awaiting.

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Обновите интерфейс страницы входа для запроса ввода паспорта.Update the Login page interface to require a passport be entered. В этом практическом занятии показано, как перевести существующую систему на использование Windows Hello. Существующие учетные записи будут иметь имя пользователя и пароль.This hands on lab demonstrates how an existing system could be migrated to use Windows Hello and existing accounts will have a username and a password. Измените объяснение в нижней части XAML, добавив пароль по умолчанию.Also update the explanation at the bottom of the XAML to include the default password. Измените следующий код XAML в файле Login.xaml.Update the following XAML in Login.xaml

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <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="PassportSignInButton" Content="Login" Background="DodgerBlue" Foreground="White"
            Click="PassportSignInButton_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="PassportStatus" Background="#22B14C"
                   Margin="0,20" Height="100">
          <TextBlock x:Name="PassportStatusText" Text="Windows Hello is ready to use!"
                 Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
        </Border>
    
        <TextBlock x:Name="LoginExplaination" 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>
    
  • В программной части кода класса Login вам необходимо изменить частную переменную Account вверху кода на UserAccount.In the Login class code behind you will need to change the Account private variable at the top of the class to be a UserAccount. Измените событие OnNavigateTo для передачи типа UserAccount.Change the OnNavigateTo event to cast the type to be a UserAccount. Вам потребуется следующая справочная информация.You will need the following reference.

    using PassportLogin.AuthService;
    
    namespace PassportLogin.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 MicrosoftPassportHelper.MicrosoftPassportAvailableCheckAsync())
                {
                    if (e.Parameter != null)
                    {
                        _isExistingAccount = true;
                        //Set the account to the existing account being passed in
                        _account = (UserAccount)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        SignInPassport();
                    }
                }
            }
        }
    }
    
  • Поскольку на странице входа используется объект UserAccount вместо предыдущего объекта Account, необходимо обновить класс MicrosoftPassportHelper.cs для использования UserAccount в качестве параметра для некоторых методов.As the Login page is using a UserAccount object instead of the previous Account object the MicrosoftPassportHelper.cs will need to be updated to use a UserAccount as a parameter for some methods. Необходимо изменить следующие параметры для методов CreatePassportKeyAsync, RemovePassportAccountAsync и GetPassportAuthenticationMessageAsync.You will need to change the following parameters for the CreatePassportKeyAsync, RemovePassportAccountAsync and GetPassportAuthenticationMessageAsync methods. Так как в классе UserAccount в качестве идентификатора пользователя используется GUID, вы будете использовать идентификатор в большем количестве случаев для большей конкретики.As the UserAccount class has a Guid for a UserId you will start using the Id in more places to be more specific.

    public static async Task<bool> CreatePassportKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    }
    
    public static async void RemovePassportAccountAsync(UserAccount account)
    {
    
    }
    public static async Task<bool> GetPassportAuthenticationMessageAsync(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 CreatePassportKey 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 CreatePassportKey method will output that error.
            if (await CreatePassportKeyAsync(account.UserId, account.Username))
            {
                //If the Passport Key was again successfully created, Windows Hello has just been reset.
                //Now that the Passport Key has been reset for the _account retry sign in.
                return await GetPassportAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Passport right now, try again later
        return false;
    }
    
  • Метод SignInPassport в файле Login.xaml.cs нужно обновить для использования AuthService вместо AccountHelper.The SignInPassport method in Login.xaml.cs file will need to be updated to use the AuthService instead of the AccountHelper. Проверка учетных данных происходит через AuthService.Validation of credentials will happen through the AuthService. В этом практическом занятии настроена всего одна учетная запись — "sampleUsername".For this hands on lab the only configured account is "sampleUsername". Эта учетная запись создается методом InitializeSampleUserAccounts в файле MockStore.cs.This account is created in the InitializeSampleUserAccounts method in MockStore.cs. Включите приведенный ниже фрагмент кода в метод SignInPassport в файле Login.xaml.cs.Update the SignInPassport method in Login.xaml.cs now to reflect the code snippet below.

    private async void SignInPassportAsync()
    {
        if (_isExistingLocalAccount)
        {
            if (await MicrosoftPassportHelper.GetPassportAuthenticationMessageAsync(_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 passport details and add them to the account
                bool isSuccessful = await MicrosoftPassportHelper.CreatePassportKeyAsync(userId, UsernameTextBox.Text);
                if (isSuccessful)
                {
                    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 passport account creation failed.
                    //Remove the account from the server as passport details were not configured
                    AuthService.AuthService.Instance.PassportRemoveUser(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Ввиду того что Windows Hello создает собственную пару открытого и закрытого ключей для каждой учетной записи на каждом устройстве, на странице приветствия необходимо отобразить список зарегистрированных устройств для текущей учетной записи и добавить возможность забыть каждое из них.As Windows Hello will create a different public and private key pair for each account on each device the Welcome page will need to display a list of registered devices for the logged in account, and allow each one to be forgotten. Добавьте следующий код XAML под элементом ForgetButton в файле Welcome.xaml.In Welcome.xaml add in the following XAML underneath the ForgetButton. Фрагмент реализует кнопку "Забыть устройство", текстовую область ошибки и список, отображающий все устройства.This will implement a forget device button, an error text area and a list to display all devices.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <TextBlock x:Name="Title" Text="Welcome" FontSize="40" TextAlignment="Center"/>
        <TextBlock x:Name="UserNameText" FontSize="28" TextAlignment="Center" Foreground="Black"/>
    
        <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>
    
  • В файле Welcome.xaml.cs нужно изменить частную переменную Account в верхней части класса на частную переменную UserAccount.In the Welcome.xaml.cs file you will need to change the private Account variable at the top of the class to be a private UserAccount variable. Теперь отредактируйте метод OnNavigatedTo для использования AuthService и получения сведений для текущей учетной записи.Then update the OnNavigatedTo method to use the AuthService and retrieve information for the current account. При наличии сведений об учетной записи вы можете задать в свойстве ItemsSource отображение списка устройств.When you have the account information you can set the itemsource of the list to display the devices. Вам потребуется добавить ссылку на пространство имен AuthService.You will need to add a reference to the AuthService namespace.

    using PassportLogin.AuthService;
    
    namespace PassportLogin.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.PassportDevices;
                        UserNameText.Text = account.Username;
                    }
                }
            }
        }
    }
    
  • Так как вы будете использовать Ауссервице при удалении учетной записи, ссылка на Аккаунселпер в кнопке забыли, что _ _ пользователь _ может удалить метод Click.As you will be using the AuthService when removing an account the reference to the AccountHelper in the Button_Forget_User_Click method can be removed. Метод должен выглядеть следующим образом.The method should now look as below.

    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        //Remove it from Windows Hello
        MicrosoftPassportHelper.RemovePassportAccountAsync(_activeAccount);
    
        Debug.WriteLine("User " + _activeAccount.Username + " deleted.");
    
        //Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Метод MicrosoftPassportHelper не использует AuthService для удаления учетной записи.The MicrosoftPassportHelper method is not using the AuthService to remove the account. Необходимо вызвать AuthService и передать идентификатор.You need to make a call to the AuthService and pass the userId.

    public static async void RemovePassportAccountAsync(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
            AuthService.AuthService.Instance.PassportRemoveUser(account.UserId);
        }
    
        //Then delete the account from the machines list of Passport Accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • Перед завершением реализации класса страницы приветствия необходимо создать в файле MicrosoftPassportHelper.cs метод, который позволит удалять устройства.Before you can finish implementing the Welcome page class, you need to create a method in MicrosoftPassportHelper.cs that will allow a device to be removed. Создайте новый метод для вызова PassportRemoveDevice в AuthService.Create a new method that will call PassportRemoveDevice in AuthService.

    public static void RemovePassportDevice(UserAccount account, Guid deviceId)
    {
        AuthService.AuthService.Instance.PassportRemoveDevice(account.UserId, deviceId);
    }
    
  • В Welcome.xaml.cs реализуйте событие нажатия кнопки "Забыть устройство".In Welcome.xaml.cs implement the Forget Device click event. Будет использоваться выбранное устройство из списка устройств и класс MicrosoftPassportHelper для вызова удаления устройства.This will use the selected device from the list of devices and use the passport helper to call remove device.

    private void Button_Forget_Device_Click(object sender, RoutedEventArgs e)
    {
        PassportDevice selectedDevice = UserListView.SelectedItem as PassportDevice;
        if (selectedDevice != null)
        {
            //Remove it from Windows Hello
            MicrosoftPassportHelper.RemovePassportDevice(_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;
        }
    }
    
  • Далее отредактируйте страницу UserSelection.The next page you will update is the UserSelection page. Страница UserSelection должна будет использовать AuthService для получения учетных записей всех пользователей для текущего устройства.The UserSelection page will need to use the AuthService to retrieve all user accounts for the current device. В данный момент вы не можете получить идентификатор устройства, передать его в AuthService и получить учетные записи пользователей для этого устройства.Currently there is no way for you get a device id to pass to the AuthService so it can return user accounts for that device. Создайте в папке Utils новый класс под названием "Helpers.cs".In the Utils folder create a new class called "Helpers.cs". Измените определение класса на общий статический (public static), а затем добавьте следующий метод, который позволит получить идентификатор текущего устройства.Change the class definition to be public static and then add the following method that will allow you to retrieve the current device id.

    using Windows.Security.ExchangeActiveSyncProvisioning;
    
    namespace PassportLogin.Utils
    {
        public static class Helpers
        {
            public static Guid GetDeviceId()
            {
                //Get the Device ID to pass to the server
                EasClientDeviceInformation deviceInformation = new EasClientDeviceInformation();
                return deviceInformation.Id;
            }
        }
    }
    
  • В классе страницы UserSelection необходимо изменить только код программной части, но не пользовательский интерфейс.In the UserSelection page class only the code behind needs to change, not the user interface. В файле UserSelection.xaml.cs отредактируйте метод loaded и метод выбора пользователя для использования класса UserAccount вместо класса Account.In UserSelection.xaml.cs update the loaded method and the user selection method to use the UserAccount class instead of the Account class. Вам также потребуется получить всех пользователей для этого устройства посредством AuthService.You will also need to get all users for this device through the AuthService.

    using System.Linq;
    using PassportLogin.AuthService;
    
    namespace PassportLogin.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 LoginPage
                    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);
                }
            }
        }
    }
    
  • Для страницы PassportRegister необходимо изменить код программной части, пользовательский интерфейс менять не нужно.The PassportRegister page needs to update the code behind, the user interface does not need changing. В файле PassportRegister.xaml.cs в верхней части класса удалите частную переменную Account, которая больше не требуется.In PassportRegister.xaml.cs remove the private Account variable at the top of the class as it is no longer needed. Обновите событие нажатия кнопки RegisterButton для использования AuthService.Update the RegisterButton click event to use the AuthService. Этот метод создает новую учетную запись, а затем пытается обновить ее сведения Passport.This method will create a new UserAccount and then try and update its passport details. Если Microsoft Passport не удается создать ключ, учетная запись удаляется по причине сбоя процесса регистрации.If passport fails to create a passport key the account will be removed as the registration process failed.

    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
            AuthService.AuthService.Instance.Register(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 passport details and add them to the account
                bool isSuccessful = await MicrosoftPassportHelper.CreatePassportKeyAsync(userId, UsernameTextBox.Text);
                if (isSuccessful)
                {
                    //Navigate to the Welcome Screen. 
                    Frame.Navigate(typeof(Welcome), AuthService.AuthService.Instance.GetUserAccount(userId));
                }
                else
                {
                    //The passport account creation failed.
                    //Remove the account from the server as passport details were not configured
                    AuthService.AuthService.Instance.PassportRemoveUser(userId);
    
                    ErrorMessage.Text = "Account Creation Failed";
                }
            }
        }
        else
        {
            ErrorMessage.Text = "Please enter a username";
        }
    }
    
  • Выполните сборку и запустите приложение (F5).Build and run the application (F5). Войдите в тестовую учетную запись, используя учетные данные "sampleUsername " и "samplePassword".Sign into the sample user account, with the credentials "sampleUsername" and "samplePassword". Вы можете заметить, что на экране приветствия отображается кнопка "Забыть устройства", но устройства отсутствуют.On the welcome screen you may notice the Forget devices button is displayed but there are no devices. При создании или переносе пользователя для работы с Windows Hello сведения паспорта не отправляются в AuthService.When you are creating or migrating a user to work with Windows Hello the passport information is not being pushed to the AuthService.

    Экран входа Windows Hello

    Успешный вход Windows Hello

  • Для передачи сведений паспорта в AuthService необходимо обновить файл MicrosoftPassportHelper.cs.To get the passport information to the AuthService the MicrosoftPassportHelper.cs will need to be updated. Метод CreatePassportKeyAsync должен не только возвращать значение true в случае успеха, но и вызывать новый метод, который попытается получить KeyAttestation.In the CreatePassportKeyAsync method, instead of only returning true in the case that it is successful, you will need to call a new method which will try to get the KeyAttestation. Несмотря на то, что в этом практическом занятии сведения в AuthService не записываются, вы узнаете, как получить их на стороне клиента.While this hands on lab is not recording this information in the AuthService you will learn how you would get it this information on the client side. Обновите метод CreatePassportKeyAsync.Update the CreatePassportKeyAsync method.

    public static async Task<bool> CreatePassportKeyAsync(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 setup!\nPlease go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • Создайте метод GetKeyAttestationAsync в классе MicrosoftPassportHelper.cs.Create this GetKeyAttestationAsync method in MicrosoftPassportHelper.cs. В данном методе показано, как получить необходимые сведения, предоставляемые Windows Hello для каждой учетной записи на конкретном устройстве.This method will demonstrate how to obtain all the necessary information that can be provided by Windows Hello for each account on a specific device.

    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 Pasport details with the information we have just gotten above.
        //UpdatePassportDetails(userId, deviceId, publicKey.ToArray(), keyAttestationResult);
    }
    
  • Вы, возможно, заметили, что в только что добавленном методе GetKeyAttestationAsync закоментирована последняя строка. Эта последняя строка станет новым методом, который вы создадите и который будет отправлять всю информацию Windows Hello в AuthService.You may have noticed in the GetKeyAttestationAsync method that you just added the last line was commented out. This last line will be a new method you create that will send all the Windows Hello information to the AuthService. В реальных сценариях данные должны отправляться на сервер с помощью веб-API.In the real world you would need to send this to an actual server with a Web API.

    using System.Runtime.InteropServices.WindowsRuntime;
    
    public static bool UpdatePassportDetails(Guid userId, Guid deviceId, byte[] publicKey, KeyCredentialAttestationResult keyAttestationResult)
    {
        //In the real world you would use an API to add Passport signing info to server for the signed in _account.
        //For this tutorial we do not implement a WebAPI for our server and simply mock the server locally 
        //The CreatePassportKey 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.
        AuthService.AuthService.Instance.PassportUpdateDetails(userId, deviceId, publicKey, keyAttestationResult);
        return true;
    }
    
  • Удалите комментарий для последней строки метода GetKeyAttestationAsync, чтобы сведения Windows Hello отправлялись в AuthService.Uncomment the last line in the GetKeyAttestationAsync method so that the Windows Hello information is being sent to the AuthService.

  • Выполните сборку, запустите приложение и войдите, используя учетные данные по умолчанию, как и раньше.Build and run the application and sign in with the default credentials as before. Теперь на экране приветствия отображается идентификатор устройства.On the welcome screen you will now see that the device Id is displayed. Если вы войдете в систему на другом устройстве, оно также появится в списке (при использовании облачной службы проверки подлинности).If you signed in on another device that would also be displayed here (if you had a cloud hosted auth service). В этом практическом занятии отображается фактический идентификатор устройства.For this hands on lab the actual device Id is being displayed. В реальном сценарии желательно использовать понятное имя, которое пользователь сможет использовать для определения каждого устройства.In a real implementation you would want to display a friendly name that a person could understand and use to determine each device.

    Идентификатор устройства, где успешно выполнен вход Windows Hello

    1. В завершение этого практического занятия необходимо запросить задачу для пользователя, если он осуществляет повторный вход со страницы выбора пользователя.To complete this hands on lab you need a request and challenge for the user when they select from the user selection page and sign back in. В AuthService есть два созданных вами метода для запроса задачи, один из которых использует подписанную задачу.The AuthService has two methods that you created to request a challenge, one that uses a signed challenge. Создайте в MicrosoftPassportHelper.cs новый метод "RequestSignAsync". Он будет запрашивать задачу в AuthService, локально подписывать ее с помощью API Passport и отправлять подписанную задачу в AuthService.In MicrosoftPassportHelper.cs create a new method called "RequestSignAsync" This will request a challenge from the AuthService, locally sign that challenge using a Passport API and send the signed challenge to the AuthService. В этом практическом занятии AuthService получает подписанную задачу и возвращает значение true.In this hands on lab the AuthService will receive the signed challenge and return true. В реальных сценариях необходимо реализовать механизм проверки, чтобы определить, что задача была подписана правильным пользователем на правильном устройстве.In an actual implementation you would need to implement a verification mechanism to determine is the challenge was signed by the correct user on the correct device. Добавьте следующий метод в файл MicrosoftPassportHelper.cs.Add the method below to the MicrosoftPassportHelper.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.PassportRequestChallenge();
        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;
    }
    
    1. В классе MicrosoftPassportHelper вызовите метод RequestSignAsync из метода GetPassportAuthenticationMessageAsync.In the MicrosoftPassportHelper class call the RequestSignAsync method from the GetPassportAuthenticationMessageAsync method.
    public static async Task<bool> GetPassportAuthenticationMessageAsync(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 CreatePassportKey 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 CreatePassportKey method will output that error.
            if (await CreatePassportKeyAsync(account.UserId, account.Username))
            {
                //If the Passport Key was again successfully created, Windows Hello has just been reset.
                //Now that the Passport Key has been reset for the _account retry sign in.
                return await GetPassportAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Windows Hello right now, try again later
        return false;
    }
    
  • В данном упражнении вы обновили клиентское приложение для использования AuthService.Throughout this exercise, you have updated the client side application to use the AuthService. Таким образом вам удалось исключить необходимость в классах Account и AccountHelper.By doing this you have been able to eliminate the need for the Account class and the AccountHelper class. Удалите класс Account, папку Models и класс AccountHelper в папке Utils.Delete the Account class, the Models folder, and the AccountHelper class in the Utils folder. Чтобы успешно выполнить сборку приложения, необходимо удалить все ссылки на пространство имен Models.You will need to remove all reference to the Models namespace throughout the application before the solution will successfully build.

  • Выполните сборку и запустите приложение. Теперь вы можете использовать Windows Hello c макетной службой и базой данных.Build and run the application and enjoy using Windows Hello with the mock service and database.

На это практическом занятии вы научились использовать API Windows Hello вместо пароля при проверке подлинности на компьютере под управлением Windows 10.In this hands on lab you have learned how to use the Windows Hello APIs to replace the need for passwords when using authenticate from a Windows 10 machine. Если учесть количество усилий, прилагаемых для хранения паролей и восстановления потерянных паролей, становится понятным преимущество перехода на новую систему проверки подлинности с помощью Windows Hello.When you consider how much energy is expended by people maintaining passwords and supporting lost passwords in existing systems, you should see the benefit of moving to this new Windows Hello system of authentication.

Мы оставили аспекты реализации проверки подлинности на стороне службы и сервера на ваше усмотрение в качестве упражнения.We have left as an exercise for you the details of how you will implement the authentication on the service and server side. Ожидается, что у большинства из вас уже есть системы, которые необходимо перенести, чтобы начать работу с Windows Hello, и у каждой из них есть свои особенности.It is expected that most of you will have existing systems that will need to be migrated to start working with Windows Hello and the details of each system will differ.