Partilhar via


Tutorial: Criar um serviço de login Windows Hello

Esta é a segunda parte de um guia completo sobre como usar o Windows Hello como alternativa aos sistemas tradicionais de autenticação por nomes de utilizador e palavras-passe em aplicações Windows integradas. Este artigo retoma onde a Parte 1, Windows Hello app de login, ficou e estende a funcionalidade para demonstrar como pode integrar Windows Hello na sua aplicação existente.

Para construir este project, vais precisar de alguma experiência em C# e XAML. Terá também de usar o Visual Studio 2022 numa máquina com Windows 10 ou Windows 11. Consulte Começar a desenvolver aplicativos do Windows para obter instruções completas sobre como configurar seu ambiente de desenvolvimento.

Exercício 1: Lógica do lado do servidor

Neste exercício, começa com a aplicação Windows Hello construída no primeiro laboratório e cria um servidor mock local e uma base de dados. Este laboratório prático foi concebido para ensinar como o Windows Hello pode ser integrado num sistema existente. Usando um servidor simulado e um banco de dados simulado, muitas configurações não relacionadas são eliminadas. Em seus próprios aplicativos, você precisará substituir os objetos fictícios pelos serviços e bancos de dados reais.

  • Para começar, abra a solução WindowsHelloLogin do primeiro Windows Hello Hands On Lab.

  • Você começará implementando o servidor fictício e o banco de dados simulado. Crie uma nova pasta chamada "AuthService". No Solution Explorer, clique com o botão direito no WindowsHelloLogin project e selecione Adicionar>Nova Pasta.

  • Crie classes UserAccount e WindowsHelloDevices que atuarão como modelos para dados a serem salvos no banco de dados fictício. A UserAccount será semelhante ao modelo de usuário implementado em um servidor de autenticação tradicional. Clique com o botão direito do mouse na pasta AuthService e adicione uma nova classe chamada "UserAccount".

    Uma captura de ecrã da criação da pasta de autorização Windows Hello

    Uma captura de ecrã da criação da nova classe para a autorização de utilizador do Windows Hello

  • Altere o escopo da classe para ser público e adicione as seguintes propriedades públicas para a classe UserAccount . Você precisará adicionar uma instrução using para o System.ComponentModel.DataAnnotations namespace.

    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();
        }
    }
    

    Você pode ter notado a lista comentada de WindowsHelloDevices. Esta é uma modificação que você precisará fazer em um modelo de usuário existente em sua implementação atual. A lista de WindowsHelloDevices conterá um deviceID, a chave pública feita a partir de Windows Hello e um KeyCredentialAttestationResult. Para este exercício, terá de implementar o keyAttestationResult pois só são fornecidos por Windows Hello em dispositivos que possuem um chip TPM (Trusted Platform Modules). O KeyCredentialAttestationResult é uma combinação de várias propriedades e precisaria ser dividido para gravá-las e carregá-las num banco de dados.

  • Crie uma nova classe na pasta AuthService chamada "WindowsHelloDevice.cs". Este é o modelo para os dispositivos Windows Hello, conforme discutido acima. Altere o escopo da classe para ser público e adicione as seguintes propriedades.

    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; }
        }
    }
    
  • Retorne a UserAccount.cs e descomente a lista de dispositivos 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();
        }
    }
    
  • Com o modelo para o UserAccount e o WindowsHelloDevice criados, você precisa criar outra nova classe na pasta AuthService que atuará como o banco de dados simulado, pois este é um banco de dados simulado de onde você salvará e carregará uma lista de contas de usuário localmente. No mundo real, esta seria a sua implementação de banco de dados. Crie uma nova classe na pasta AuthService chamada "MockStore.cs". Altere o escopo da classe para pública.

  • Como o armazenamento simulado salvará e carregará uma lista de contas de usuário localmente, você pode implementar a lógica para salvar e carregar essa lista usando um XmlSerializer. Você também precisará lembrar o nome do arquivo e salvar o local. Em MockStore.cs implementar o seguinte:

    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
        }
    }
    
  • No método LoadAccountListAsync , você deve ter notado que um método InitializeSampleUserAccountsAsync foi comentado. Você precisará criar esse método no MockStore.cs. Esse método preencherá a lista de contas de usuário para que um login possa ocorrer. No mundo real, o banco de dados de usuários já estaria preenchido. Nesta etapa, você também criará um construtor que inicializará a lista de usuários e chamará 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();
            }
        }
    }
    
  • Agora que o método InitializeSampleUserAccountsAsync existe, descomente a chamada do método no método 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();
        }
    }
    
  • A lista de contas de usuário no repositório simulado agora pode ser salva e carregada. Outras partes da aplicação precisarão de acesso a esta lista, pelo que serão necessários métodos para recuperar estes dados. Abaixo do método InitializeSampleUserAccountsAsync , adicione os seguintes métodos para obter dados. Permitem obter um ID de utilizador, um único utilizador, uma lista de utilizadores para um dispositivo Windows Hello específico, e também obter a chave pública do utilizador num dispositivo específico.

    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;
    }
    
  • Os próximos métodos a serem implementados lidarão com operações simples para adicionar uma conta, remover uma conta e também remover um dispositivo. Remover um dispositivo é necessário, pois o Windows Hello é específico de cada dispositivo. Para cada dispositivo em que iniciar sessão, um novo par de chaves públicas e privadas será criado pelo Windows Hello. É como ter uma senha diferente para cada dispositivo em que você entra, a única coisa é que você não precisa se lembrar de todas essas senhas; o servidor faz. Adicione os seguintes métodos ao 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;
    }
    
  • Na classe MockStore, adiciona um método que incluirá informações relacionadas ao Windows Hello a um UserAccount existente. Este método será chamado "WindowsHelloUpdateDetailsAsync" e terá parâmetros para identificar o utilizador e os detalhes do Windows Hello. O KeyAttestationResult foi comentado ao criar um WindowsHelloDevice, em um aplicativo do mundo real você precisaria disso.

    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();
    }
    
  • A classe MockStore agora está completa, pois representa o banco de dados que deve ser considerado privado. Para aceder à MockStore, é necessária uma classe AuthService para manipular os dados da base de dados. Na pasta AuthService , crie uma nova classe chamada "AuthService.cs". Altere o escopo da classe para public e adicione um padrão de instância singleton para garantir que apenas uma instância seja criada.

    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()
            { }
        }
    }
    
  • A classe AuthService precisa de criar uma instância da classe MockStore e fornecer access às propriedades do objeto 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);
            }
        }
    }
    
  • Precisas de métodos na classe AuthService para aceder, adicionar, remover e atualizar detalhes do Windows Hello no objeto MockStore. No final da definição de classe AuthService , adicione os seguintes métodos.

    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);
    }
    
  • A classe AuthService precisa fornecer um método para validar credenciais. Este método irá utilizar um nome de utilizador e uma palavra-passe e certificar-se de que a conta existe e que a palavra-passe é válida. Um sistema existente teria um método equivalente a este que verifica se o usuário está autorizado. Adicione o seguinte método ValidateCredentials ao arquivo 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;
    }
    
  • A classe AuthService precisa de um método de desafio de solicitação que retorna um desafio ao cliente para validar se o usuário é quem afirma ser. Em seguida, outro método é necessário na classe AuthService para receber o desafio assinado de volta do cliente. Para este laboratório prático, o método de como você determina se o desafio assinado foi concluído foi deixado incompleto. Cada implementação do Windows Hello num sistema de autenticação existente será ligeiramente diferente. A chave pública armazenada no servidor precisa corresponder ao resultado que o cliente retornou ao servidor. Adicione estes dois métodos a 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;
    }
    

Exercício 2: Lógica do lado do cliente

Neste exercício, você alterará as visualizações do lado do cliente e as classes auxiliares do primeiro laboratório para usar a classe AuthService . No mundo real, o AuthService seria o servidor de autenticação e você precisaria usar APIs da Web para enviar e receber dados do servidor. Para este laboratório prático, o cliente e o servidor são locais de forma a simplificar. O objetivo é aprender a usar as APIs do Windows Hello.

  • No MainPage.xaml.cs, você pode remover a chamada do método AccountHelper.LoadAccountListAsync no método carregado, pois a classe AuthService cria uma instância do MockStore para carregar a lista de contas. O Loaded método agora deve se parecer com o trecho abaixo. Observe que a definição do método assíncrono é removida, pois nada está sendo aguardado.

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
  • Atualize a interface da página de login para exigir que uma senha seja inserida. Este laboratório prático demonstra como um sistema existente pode ser migrado para usar o Windows Hello e as contas existentes terão um nome de utilizador e uma palavra-passe. Atualize também a explicação na parte inferior do XAML para incluir a senha padrão. Atualizar o XAML seguinte no ficheiro 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>
    
  • No arquivo code-behind para a classe Login, você precisará alterar a variável privada no topo da classe para ser um Account. Altere o evento OnNavigateTo para mudar o tipo para um UserAccount. Você também precisará da seguinte declaração using.

    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();
            }
        }
    }
    
  • Como a página Login está usando um UserAccount objeto em vez do objeto anterior Account , o WindowsHelloHelper.cs precisará ser atualizado para usar um UserAccount como um tipo de parâmetro para alguns métodos. Você precisará alterar os seguintes parâmetros para os métodos CreateWindowsHelloKeyAsync, RemoveWindowsHelloAccountAsync e GetWindowsHelloAuthenticationMessageAsync . Como a UserAccount classe tem um Guid para um UserId, você começará a usar o Id em mais lugares para ser mais preciso.

    public static async Task<bool> CreateWindowsHelloKeyAsync(Guid userId, string username)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
    
        return true;
    }
    
    public static async Task 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;
    }
    
  • O método SignInWindowsHelloAsync em Login.xaml.cs arquivo precisará ser atualizado para usar o AuthService em vez do AccountHelper. A validação das credenciais acontecerá através do AuthService. Para este laboratório prático, a única conta configurada é "nomeDeUsuarioExemplo". Essa conta é criada no método InitializeSampleUserAccountsAsync no MockStore.cs. Atualize o método SignInWindowsHelloAsync no Login.xaml.cs agora para refletir o trecho de código abaixo.

    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";
        }
    }
    
  • Como o Windows Hello criará um par de chaves públicas e privadas diferentes para cada conta em cada dispositivo, a página Bem-vindo terá de mostrar uma lista de dispositivos associados à conta iniciada e permitir que cada um seja esquecido. Em Welcome.xaml, adicione o seguinte XAML abaixo do ForgetButton. Isso implementará um botão esquecer dispositivo, uma área de texto de erro e uma lista para exibir todos os dispositivos.

    <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>
    
  • No arquivo Welcome.xaml.cs, você precisa alterar a variável private Account na parte superior da classe para ser uma variável private UserAccount . Em seguida, atualize o OnNavigatedTo método para usar o AuthService e recuperar informações para a conta atual. Quando tiver as informações da conta, pode definir o ItemsSource da lista para exibir os dispositivos. Você precisará adicionar uma referência ao namespace 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;
                    }
                }
            }
        }
    }
    
  • Como você estará usando o AuthService ao remover uma conta, a referência ao AccountHelper no Button_Forget_User_Click método pode ser removida. O método deve agora ter o aspeto seguinte.

    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));
    }
    
  • O método WindowsHelloHelper não está usando o AuthService para remover a conta. Você precisa fazer uma chamada para o AuthService e passar o userId.

    public static async Task 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);
    }
    
  • Antes de concluir a implementação da página de boas-vindas , você precisa criar um método em WindowsHelloHelper.cs que permita que um dispositivo seja removido. Crie um novo método que chamará WindowsHelloRemoveDeviceAsync em AuthService.

    public static async Task RemoveWindowsHelloDeviceAsync(UserAccount account, Guid deviceId)
    {
        await AuthService.AuthService.Instance.WindowsHelloRemoveDeviceAsync(account.UserId, deviceId);
    }
    
  • Em Welcome.xaml.cs, implemente o manipulador de eventos Button_Forget_Device_Click. Isto irá usar o dispositivo selecionado da lista de dispositivos e usar o assistente do Windows Hello para chamar a função de remoção de dispositivo. Lembre-se de transformar o manipulador de eventos em assíncrono.

    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;
        }
    }
    
  • A próxima página que você atualizará é a página UserSelection . A página UserSelection precisará usar o AuthService para recuperar todas as contas de usuário do dispositivo atual. Atualmente, não há como obter um ID de dispositivo para passar para o AuthService para que ele possa retornar contas de usuário para esse dispositivo. Na pasta Utils , crie uma nova classe chamada "Helpers.cs". Altere o escopo da classe para ser estático público e, em seguida, adicione o seguinte método que permitirá recuperar a ID do dispositivo atual.

    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;
            }
        }
    }
    
  • Na classe de página UserSelection, apenas o "code-behind" (código por trás) precisa ser alterado, não a interface do usuário. No UserSelection.xaml.cs, atualize o método UserSelection_Loaded e o método UserSelectionChanged para usar a UserAccount classe em vez da Account classe. Você também precisará obter todos os usuários para este dispositivo através do 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);
                }
            }
        }
    }
    
  • A página WindowsHelloRegister precisa ter o arquivo code-behind atualizado. A interface do usuário não precisa de alterações. Em WindowsHelloRegister.xaml.cs, remova a variável privada Account na parte superior da classe, pois ela não é mais necessária. Atualize o manipulador de eventos RegisterButton_Click_Async para usar o AuthService. Esse método criará uma nova UserAccount e, em seguida, tentará atualizar os detalhes da conta. Se o Windows Hello não conseguir criar uma chave, a conta será removida, pois o processo de registo falhou.

    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";
        }
    }
    
  • Compile e execute o aplicativo. Entre na conta de usuário de exemplo com as credenciais "sampleUsername" e "samplePassword". Na tela de boas-vindas, você pode notar que o botão Esquecer dispositivos é exibido, mas não há dispositivos. Quando está a criar ou migrar um utilizador para trabalhar com Windows Hello a informação da conta não está a ser enviada para o AuthService.

    Uma captura de ecrã do ecrã de login Windows Hello

    Uma captura de ecrã do login Windows Hello concluído com sucesso

  • Para enviar a informação Windows Hello da conta para o AuthService, o WindowsHelloHelper.cs terá de ser atualizado. No método CreateWindowsHelloKeyAsync , em vez de retornar true apenas no caso de sucesso, você precisará chamar um novo método que tentará obter o KeyAttestation. Embora este laboratório prático não esteja gravando essas informações no AuthService, você aprenderá como obteria essas informações no lado do cliente. Atualize o método CreateWindowsHelloKeyAsync da seguinte maneira:

    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;
    }
    
  • Crie um método GetKeyAttestationAsync no WindowsHelloHelper.cs. Este método irá demonstrar como obter toda a informação necessária que o Windows Hello pode fornecer para cada conta num dispositivo específico.

    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);
    }
    
  • Talvez tenhas reparado no método GetKeyAttestationAsync que acabaste de adicionar a última linha foi comentada. Esta última linha será um novo método que crias e que enviará toda a informação de Windows Hello para o AuthService. No mundo real, você precisaria enviar isso para um servidor real por meio de uma API da 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;
    }
    
  • Descomentar a última linha do método GetKeyAttestationAsync para que a informação Windows Hello seja enviada para o AuthService.

  • Crie e execute o aplicativo e entre com as credenciais padrão como antes. Na página Bem-vindo , você verá que a ID do dispositivo é exibida. Se você entrou em outro dispositivo, isso também seria exibido aqui (se você tivesse um serviço de autenticação hospedado na nuvem). Neste laboratório prático, o identificador real do dispositivo está a ser exibido. Em uma implementação real, você gostaria de exibir um nome amigável que uma pessoa poderia entender e usar para identificar cada dispositivo.

    Uma captura de ecrã do Windows Hello login bem-sucedido mostrando o ID do dispositivo

  • Para concluir este laboratório prático, você precisa de uma solicitação e desafio para o usuário quando ele seleciona na página de seleção de usuário e entra novamente. O AuthService tem dois métodos que você criou para solicitar um desafio, um que usa um desafio assinado. No WindowsHelloHelper.cs, crie um novo método chamado RequestSignAsync. Isto irá pedir um desafio ao AuthService, assinar localmente esse desafio usando uma API Windows Hello e enviar o desafio assinado para o AuthService. Neste exercício prático, a AuthService receberá o desafio assinado e retornará true. Em uma implementação real, você precisaria implementar um mecanismo de verificação para determinar se o desafio foi assinado pelo usuário correto no dispositivo correto. Adicione o método abaixo ao 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;
    }
    
  • Na classe WindowsHelloHelper , chame o método RequestSignAsync do método 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;
    }
    
  • Ao longo deste exercício, você atualizou o aplicativo do lado do cliente para usar o AuthService. Ao fazer isso, você conseguiu eliminar a necessidade da classe Account e da classe AccountHelper . Exclua a classe Account , a pasta Models e a classe AccountHelper na pasta Utils . Você precisará remover todas as referências ao namespace WindowsHelloLogin.Models em toda a aplicação, antes que a solução seja compilada com êxito.

  • Constrói e executa a aplicação e desfruta de usar o Windows Hello com o serviço de simulação e a base de dados.

Neste laboratório prático, aprendeu a usar as APIs do Windows Hello para substituir a necessidade de palavras-passe ao usar autenticação a partir de uma máquina Windows. Quando se considera a quantidade de energia despendida pelas pessoas que mantêm palavras-passe e suportam a recuperação de palavras-passe perdidas em sistemas existentes, perceberá a vantagem de transitar para este novo sistema de autenticação Windows Hello.

Deixamos como um exercício para você os detalhes de como você implementará a autenticação no lado do serviço e do servidor. Espera-se que a maioria dos programadores tenha sistemas existentes que terão de ser migrados para começar a funcionar com o Windows Hello. Os detalhes de cada um destes sistemas serão diferentes.