Compartir a través de


Crear una aplicación de inicio de sesión de Windows Hello

Esta es la primera parte de un tutorial completo sobre cómo crear una aplicación de Windows empaquetada que usa Windows Hello como alternativa a los sistemas tradicionales de autenticación de nombre de usuario y contraseña. En este caso, la aplicación es una aplicación winUI, pero el mismo enfoque se puede usar con cualquier aplicación de Windows empaquetada, incluidas las aplicaciones de WPF y Windows Forms. La aplicación usa un nombre de usuario para el inicio de sesión y crea una clave Hello para cada cuenta. Estas cuentas se protegerán mediante el PIN configurado en Configuración de Windows en la configuración de Windows Hello.

Este tutorial se divide en dos partes: compilar la aplicación y conectar el servicio back-end. Cuando haya terminado con este artículo, continúe con la parte 2: servicio de inicio de sesión de Windows Hello.

Antes de empezar, debe leer la información general de Windows Hello para obtener información general sobre cómo funciona Windows Hello.

Introducción

Para compilar este proyecto, necesitarás experiencia con C#y XAML. También deberá usar Visual Studio 2022 en una máquina Windows 10 o Windows 11. Consulte Introducción a WinUI para obtener instrucciones completas sobre cómo configurar el entorno de desarrollo.

  • Abra Visual Studio, seleccione Archivo>Nuevo>Proyecto.
  • En los filtros desplegables del cuadro de diálogo Nuevo proyecto , seleccione C#/C++, Windows y WinUI, respectivamente.
  • Elija Aplicación en blanco, Empaquetada (WinUI 3 en escritorio) y asigne un nombre a la aplicación "WindowsHelloLogin".
  • Compile y ejecute la nueva aplicación (F5), debería ver una ventana en blanco que se muestra en la pantalla. Cierre la aplicación.

Captura de pantalla de la nueva aplicación de inicio de sesión de Windows Hello que se ejecuta por primera vez

Ejercicio 1: Inicio de sesión con Windows Hello

En este ejercicio aprenderá a comprobar si Windows Hello está configurado en la máquina y a iniciar sesión en una cuenta con Windows Hello.

  • En el nuevo proyecto, cree una nueva carpeta en la solución denominada "Vistas". Esta carpeta contendrá las páginas a las que se navegará en este ejemplo. Haga clic con el botón derecho en el proyecto en Explorador de soluciones, seleccione Agregar>nueva carpeta y cambie el nombre de la carpeta a Vistas.

    Captura de pantalla de la adición de una nueva carpeta denominada Views al proyecto de inicio de sesión de Windows Hello

  • Abra MainWindow.xaml y reemplace el Window contenido por un control o Grid vacíoStackPanel. Vamos a implementar la navegación de página y navegar a una nueva página cuando se cargue MainWindow , por lo que no necesitamos ningún contenido en MainWindow.

  • Agregue una Title propiedad a MainWindow en el XAML. El atributo debe tener este aspecto: Title="Windows Hello Login".

  • Quite el controlador de eventos myButton_Click de MainWindow.xaml.cs para evitar errores de compilación. Este controlador de eventos no es necesario para este ejemplo.

  • Haga clic con el botón derecho en la nueva carpeta Vistas, seleccione Agregar>nuevo elemento y seleccione la plantilla Página en blanco. Asigne a esta página el nombre "MainPage.xaml".

    Captura de pantalla de cómo agregar una nueva página en blanco al proyecto inicio de sesión de Windows Hello

  • Abra el archivo App.xaml.cs y actualice el controlador OnLaunched para implementar la navegación de páginas para la aplicación. También deberá agregar un método de controlador de RootFrame_NavigationFailed para tratar los errores que se producen al cargar páginas.

    protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
    {
        m_window = new MainWindow();
        var rootFrame = new Frame();
        rootFrame.NavigationFailed += RootFrame_NavigationFailed;
        rootFrame.Navigate(typeof(MainPage), args);
        m_window.Content = rootFrame;
        m_window.Activate();
    }
    
    private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
    {
        throw new Exception($"Error loading page {e.SourcePageType.FullName}");
    }
    
  • También deberá agregar cuatro instrucciones using a la parte superior del archivo App.xaml.cs para resolver errores de compilación en el código.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using WindowsHelloLogin.Views;
    
  • Haga clic con el botón derecho en la nueva carpeta Vistas, seleccione Agregar>nuevo elemento y seleccione la plantilla Página en blanco. Asigne a esta página el nombre "Login.xaml".

  • Para definir la interfaz de usuario para la nueva página de inicio de sesión, agregue el código XAML siguiente. Este XAML define un StackPanel para alinear los elementos secundarios siguientes:

    • que TextBlock contendrá un título.

    • para TextBlock mensajes de error.

    • para TextBox que el nombre de usuario sea de entrada.

    • Button para ir a una página de registro.

    • TextBlock que contiene el estado de Windows Hello.

    • para TextBlock explicar la página Inicio de sesión, ya que aún no hay ningún back-end ni usuarios configurados.

      <Grid>
        <StackPanel>
          <TextBlock Text="Login" FontSize="36" Margin="4" TextAlignment="Center"/>
          <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
          <TextBlock Text="Enter your username below" Margin="0,0,0,20"
                     TextWrapping="Wrap" Width="300"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
          <Button x:Name="LoginButton" Content="Login" Background="DodgerBlue" Foreground="White"
                  Click="LoginButton_Click" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
          <TextBlock Text="Don't have an account?"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <TextBlock x:Name="RegisterButtonTextBlock" Text="Register now"
                     PointerPressed="RegisterButtonTextBlock_OnPointerPressed"
                     Foreground="DodgerBlue"
                     TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
          <Border x:Name="WindowsHelloStatus" Background="#22B14C"
                  Margin="0,20" Height="100" >
            <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!"
                       Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
          </Border>
          <TextBlock x:Name="LoginExplanation" FontSize="24" TextAlignment="Center" TextWrapping="Wrap" 
                     Text="Please Note: To demonstrate a login, validation will only occur using the default username 'sampleUsername'"/>
        </StackPanel>
      </Grid>
      
  • Es necesario agregar algunos métodos al archivo de código subyacente para obtener la compilación de la solución. Presione F7 o use el Explorador de soluciones para editar el archivo Login.xaml.cs. Agregue los dos métodos de evento siguientes para controlar los eventos Login y Register . Por ahora, estos métodos establecerán en ErrorMessage.Text una cadena vacía. Asegúrese de incluir las siguientes instrucciones using. Serán necesarios para los pasos siguientes.

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Input;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            public Login()
            {
                this.InitializeComponent();
            }
            private void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
        }
    }
    
  • Para representar la página Inicio de sesión, edite el código MainPage para ir a la página Inicio de sesión cuando se carga MainPage. Abre el archivo MainPage.xaml.cs. En Explorador de soluciones, haga doble clic en MainPage.xaml.cs. Si no encuentras esto, haz clic en la pequeña flecha situada junto a MainPage.xaml para mostrar el archivo de código subyacente. Cree un método de controlador de eventos cargado que vaya a la página Inicio de sesión .

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • En la página Inicio de sesión , debe controlar el OnNavigatedTo evento para validar si Windows Hello está disponible en la máquina actual. En Login.xaml.cs, implemente el código siguiente. Observará que el objeto WindowsHelloHelper indica que hay un error. Esto se debe a que aún no hemos creado esta clase auxiliar.

    public sealed partial class Login : Page
    {
        public Login()
        {
            this.InitializeComponent();
        }
    
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            // Check if Windows Hello is set up and available on this machine
            if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
            {
            }
            else
            {
                // Windows Hello isn't set up, so inform the user
                WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                LoginButton.IsEnabled = false;
            }
        }
    }
    
  • Para crear la clase WindowsHelloHelper, haga clic con el botón derecho en el proyecto WindowsHelloLogin y haga clic en Agregar>nueva carpeta. Asigne a esta carpeta el nombre Utils.

  • Haga clic con el botón derecho en la carpeta Utils y seleccione Agregar>clase. Asigne a esta nueva clase el nombre "WindowsHelloHelper.cs".

    Captura de pantalla de la creación de la clase auxiliar de inicio de sesión de Windows Hello

  • Cambie el ámbito de la clase WindowsHelloHelper para que sea public static, agregue el siguiente método que le informará al usuario si Windows Hello está listo para usarse o no. Deberá agregar los espacios de nombres necesarios.

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class WindowsHelloHelper
        {
            /// <summary>
            /// Checks to see if Windows Hello is ready to be used.
            /// 
            /// Windows Hello has dependencies on:
            ///     1. Having a connected Microsoft Account
            ///     2. Having a Windows PIN set up for that account on the local machine
            /// </summary>
            public static async Task<bool> WindowsHelloAvailableCheckAsync()
            {
                bool keyCredentialAvailable = await KeyCredentialManager.IsSupportedAsync();
                if (keyCredentialAvailable == false)
                {
                    // Key credential is not enabled yet as user 
                    // needs to connect to a Microsoft Account and select a PIN in the connecting flow.
                    Debug.WriteLine("Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.");
                    return false;
                }
    
                return true;
            }
        }
    }
    
  • En Login.xaml.cs, agregue una referencia al WindowsHelloLogin.Utils espacio de nombres. Esto resolverá el error en el OnNavigatedTo método .

    using WindowsHelloLogin.Utils;
    
  • Compile y ejecute la aplicación. Se le navegará a la página de inicio de sesión y el banner de Windows Hello le indicará si Windows Hello está listo para usarse. Debería ver el banner verde o azul que indica el estado de Windows Hello en el equipo.

    Captura de pantalla de la pantalla de inicio de sesión de Windows Hello con un estado listo

  • Lo siguiente que debe hacer es crear la lógica para iniciar sesión. Cree una nueva carpeta en el proyecto denominado "Models".

  • En la carpeta Models , cree una nueva clase denominada "Account.cs". Esta clase actuará como modelo de cuenta. Como se trata de un proyecto de ejemplo, solo contendrá un nombre de usuario. Cambie el ámbito de clase a public y agregue la Username propiedad .

    namespace WindowsHelloLogin.Models
    {
        public class Account
        {
            public string Username { get; set; }
        }
    }
    
  • La aplicación necesita una manera de controlar las cuentas. Para este laboratorio práctico, ya que no hay ningún servidor o base de datos, se guarda y carga localmente una lista de usuarios. Haga clic con el botón derecho en la carpeta Utils y agregue una nueva clase denominada "AccountHelper.cs". Cambie el ámbito de clase para que sea public static. AccountHelper es una clase estática que contiene todos los métodos necesarios para guardar y cargar la lista de cuentas localmente. Guardar y cargar funciona mediante xmlSerializer. También debe recordar el archivo que se guardó y dónde lo guardó.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    using WindowsHelloLogin.Models;
    
    namespace WindowsHelloLogin.Utils
    {
        public static class AccountHelper
        {
            // In the real world this would not be needed as there would be a server implemented that would host a user account database.
            // For this tutorial we will just be storing accounts locally.
            private const string USER_ACCOUNT_LIST_FILE_NAME = "accountlist.txt";
            private static string _accountListPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, USER_ACCOUNT_LIST_FILE_NAME);
            public static List<Account> AccountList = [];
    
            /// <summary>
            /// Create and save a useraccount list file. (Updating the old one)
            /// </summary>
            private static async void SaveAccountListAsync()
            {
                string accountsXml = SerializeAccountListToXml();
    
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
                else
                {
                    StorageFile accountsFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(USER_ACCOUNT_LIST_FILE_NAME);
                    await FileIO.WriteTextAsync(accountsFile, accountsXml);
                }
            }
    
            /// <summary>
            /// Gets the useraccount list file and deserializes it from XML to a list of useraccount objects.
            /// </summary>
            /// <returns>List of useraccount objects</returns>
            public static async Task<List<Account>> LoadAccountListAsync()
            {
                if (File.Exists(_accountListPath))
                {
                    StorageFile accountsFile = await StorageFile.GetFileFromPathAsync(_accountListPath);
    
                    string accountsXml = await FileIO.ReadTextAsync(accountsFile);
                    DeserializeXmlToAccountList(accountsXml);
                }
    
                return AccountList;
            }
    
            /// <summary>
            /// Uses the local list of accounts and returns an XML formatted string representing the list
            /// </summary>
            /// <returns>XML formatted list of accounts</returns>
            public static string SerializeAccountListToXml()
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                var writer = new StringWriter();
                xmlizer.Serialize(writer, AccountList);
    
                return writer.ToString();
            }
    
            /// <summary>
            /// Takes an XML formatted string representing a list of accounts and returns a list object of accounts
            /// </summary>
            /// <param name="listAsXml">XML formatted list of accounts</param>
            /// <returns>List object of accounts</returns>
            public static List<Account> DeserializeXmlToAccountList(string listAsXml)
            {
                var xmlizer = new XmlSerializer(typeof(List<Account>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
    
                return AccountList = (xmlizer.Deserialize(textreader)) as List<Account>;
            }
        }
    }
    
  • A continuación, implemente una manera de agregar y quitar una cuenta de la lista local de cuentas. Estas acciones guardarán cada una de las acciones de la lista. El método final que necesitará para este laboratorio práctico es un método de validación. Como no hay ningún servidor de autorización o base de datos de usuarios, esto se validará con un único usuario que esté codificado de forma rígida. Estos métodos se deben agregar a la clase AccountHelper .

    public static Account AddAccount(string username)
    {
        // Create a new account with the username
        var account = new Account() { Username = username };
        // Add it to the local list of accounts
        AccountList.Add(account);
        // SaveAccountList and return the account
        SaveAccountListAsync();
        return account;
    }
    
    public static void RemoveAccount(Account account)
    {
        // Remove the account from the accounts list
        AccountList.Remove(account);
        // Re save the updated list
        SaveAccountListAsync();
    }
    
    public static bool ValidateAccountCredentials(string username)
    {
        // In the real world, this method would call the server to authenticate that the account exists and is valid.
        // However, for this tutorial, we'll just have an existing sample user that's named "sampleUsername".
        // If the username is null or does not match "sampleUsername" validation will fail. 
        // In this case, the user should register a new Windows Hello user.
    
        if (string.IsNullOrEmpty(username))
        {
            return false;
        }
    
        if (!string.Equals(username, "sampleUsername"))
        {
            return false;
        }
    
        return true;
    }
    
  • Lo siguiente que debe hacer es controlar una solicitud de inicio de sesión del usuario. En Login.xaml.cs, cree una nueva variable privada que contenga el registro de la cuenta actual. A continuación, agregue un nuevo método denominado SignInWindowsHelloAsync. Esto validará las credenciales de la cuenta mediante el método AccountHelper.ValidateAccountCredentials . Este método devolverá un valor booleano si el nombre de usuario especificado es el mismo que el valor de cadena codificado de forma rígida que configuró en el paso anterior. El valor codificado de forma rígida para este ejemplo es "sampleUsername".

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check if Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = "Windows Hello is not set up!\nPlease go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
    
            private async void LoginButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                await SignInWindowsHelloAsync();
            }
    
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
    
            private async Task SignInWindowsHelloAsync()
            {
                if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
                {
                    // Create and add a new local account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
                    //if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
                    //{
                    //    Debug.WriteLine("Successfully signed in with Windows Hello!");
                    //}
                }
                else
                {
                    ErrorMessage.Text = "Invalid Credentials";
                }
            }
        }
    }
    
  • Es posible que haya observado el código comentado que hacía referencia a un método en WindowsHelloHelper. En WindowsHelloHelper.cs, agregue un nuevo método denominado CreateWindowsHelloKeyAsync. Este método usa la API de Windows Hello en KeyCredentialManager. Al llamar a RequestCreateAsync , se creará una clave de Windows Hello específica del accountId y del equipo local. Tenga en cuenta los comentarios de la instrucción switch si está interesado en implementarlo en un escenario real.

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

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile y ejecute la aplicación. Se le llevará a la página Inicio de sesión. Escriba el nombre de usuario como "sampleUsername" y haga clic en Iniciar sesión. Se le pedirá un mensaje de Windows Hello que le pida que escriba el PIN. Al escribir el PIN correctamente, el método CreateWindowsHelloKeyAsync podrá crear una clave de Windows Hello. Supervise las ventanas de salida para ver si se muestran los mensajes que indican que se ha realizado correctamente.

    Captura de pantalla del aviso del pin de inicio de sesión de Windows Hello

Ejercicio 2: Páginas de selección de usuario y bienvenida

En este ejercicio, continuará desde el ejercicio anterior. Cuando un usuario inicia sesión correctamente, se le debe llevar a una página principal donde puede cerrar la sesión o eliminar su cuenta. A medida que Windows Hello crea una clave para cada máquina, se puede crear una pantalla de selección de usuario, que muestra todos los usuarios que han iniciado sesión en esa máquina. A continuación, un usuario puede seleccionar una de estas cuentas y ir directamente a la pantalla de bienvenida sin necesidad de volver a escribir una contraseña, ya que ya se han autenticado para acceder a la máquina.

  • En la carpeta Vistas , agregue una nueva página en blanco denominada "Welcome.xaml". Agregue el código XAML siguiente para completar la interfaz de usuario de la página. Se mostrará un título, el nombre de usuario que inició sesión y dos botones. Uno de los botones volverá a una lista de usuarios (que creará más adelante) y el otro botón controlará el olvido de este usuario.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Welcome" FontSize="40" TextAlignment="Center"/>
        <TextBlock x:Name="UserNameText" FontSize="28" TextAlignment="Center"/>
    
        <Button x:Name="BackToUserListButton" Content="Back to User List" Click="Button_Restart_Click"
                HorizontalAlignment="Center" Margin="0,20" Foreground="White" Background="DodgerBlue"/>
    
        <Button x:Name="ForgetButton" Content="Forget Me" Click="Button_Forget_User_Click"
                Foreground="White"
                Background="Gray"
                HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • En el Welcome.xaml.cs archivo de código subyacente, agregue una nueva variable privada que contendrá la cuenta que ha iniciado sesión. Tendrá que implementar un método para invalidar el OnNavigateTo evento; esto almacenará la cuenta pasada a la página principal . También tendrás que implementar el Click evento para los dos botones definidos en xaml. Deberá agregar instrucciones using para los WindowsHelloLogin.Models espacios de nombres y WindowsHelloLogin.Utils .

    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    using System.Diagnostics;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Welcome : Page
        {
            private Account _activeAccount;
    
            public Welcome()
            {
                InitializeComponent();
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                _activeAccount = (Account)e.Parameter;
                if (_activeAccount != null)
                {
                    UserNameText.Text = _activeAccount.Username;
                }
            }
    
            private void Button_Restart_Click(object sender, RoutedEventArgs e)
            {
            }
    
            private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
            {
                // Remove the account from Windows Hello
                // WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
                // Remove it from the local accounts list and re-save the updated list
                AccountHelper.RemoveAccount(_activeAccount);
    
                Debug.WriteLine($"User {_activeAccount.Username} deleted.");
            }
        }
    }
    
  • Es posible que haya observado una línea comentada en el controlador de Button_Forget_User_Click eventos. La cuenta se va a quitar de la lista local, pero actualmente no hay ninguna manera de quitarse de Windows Hello. Debes implementar un nuevo método en WindowsHelloHelper.cs que controlará la eliminación de un usuario de Windows Hello. Este método usará otras API de Windows Hello para abrir y eliminar la cuenta. En el mundo real, al eliminar una cuenta, el servidor o la base de datos deben recibir una notificación para que la base de datos de usuario siga siendo válida. Necesitará una instrucción using que haga referencia al WindowsHelloLogin.Models espacio de nombres .

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

    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    }
    
  • En el método SignInWindowsHelloAsync (en Login.xaml.cs), una vez que createWindowsHelloKeyAsync se realiza correctamente, debe navegar a la página principal y pasar la cuenta.

    private async void SignInWindowsHelloAsync()
    {
        if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            // Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile y ejecute la aplicación. Inicie sesión con "sampleUsername" y haga clic en Inicio de sesión. Escriba el PIN y, si se ha realizado correctamente, debe ir a la pantalla de bienvenida . Intente hacer clic en Olvidar usuario y supervisar la ventana Salida de Visual Studio para ver si se eliminó el usuario. Tenga en cuenta que cuando se elimina el usuario, permanece en la página principal . Deberá crear una página de selección de usuario a la que puede navegar la aplicación.

    Captura de pantalla de la pantalla de bienvenida de Windows Hello

  • En la carpeta Vistas , cree una nueva página en blanco denominada "UserSelection.xaml" y agregue el siguiente XAML para definir la interfaz de usuario. Esta página contendrá un control ListView que muestra todos los usuarios de la lista de cuentas locales y un Button que navegará a la página Inicio de sesión para permitir que el usuario agregue otra cuenta.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Select a User" FontSize="36" Margin="4" TextAlignment="Center" HorizontalAlignment="Center"/>
    
        <ListView x:Name="UserListView" Margin="4" MaxHeight="200" MinWidth="250" Width="250" HorizontalAlignment="Center">
          <ListView.ItemTemplate>
            <DataTemplate>
              <Grid Background="DodgerBlue" Height="50" Width="250" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <TextBlock Text="{Binding Username}" HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
              </Grid>
            </DataTemplate>
          </ListView.ItemTemplate>
        </ListView>
    
        <Button x:Name="AddUserButton" Content="+" FontSize="36" Width="60" Click="AddUserButton_Click" HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • En UserSelection.xaml.cs, implemente el Loaded método que navegará a la página Inicio de sesión si no hay ninguna cuenta en la lista local. Implemente también el SelectionChanged evento para y ListView un Click evento para .Button

    using System.Diagnostics;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class UserSelection : Page
        {
            public UserSelection()
            {
                InitializeComponent();
                Loaded += UserSelection_Loaded;
            }
    
            private void UserSelection_Loaded(object sender, RoutedEventArgs e)
            {
                if (AccountHelper.AccountList.Count == 0)
                {
                    // If there are no accounts, navigate to the Login page
                    Frame.Navigate(typeof(Login));
                }
    
    
                UserListView.ItemsSource = AccountHelper.AccountList;
                UserListView.SelectionChanged += UserSelectionChanged;
            }
    
            /// <summary>
            /// Function called when an account is selected in the list of accounts
            /// Navigates to the Login page and passes the chosen account
            /// </summary>
            private void UserSelectionChanged(object sender, RoutedEventArgs e)
            {
                if (((ListView)sender).SelectedValue != null)
                {
                    Account account = (Account)((ListView)sender).SelectedValue;
                    if (account != null)
                    {
                        Debug.WriteLine($"Account {account.Username} selected!");
                    }
                    Frame.Navigate(typeof(Login), account);
                }
            }
    
            /// <summary>
            /// Function called when the "+" button is clicked to add a new user.
            /// Navigates to the Login page with nothing filled out
            /// </summary>
            private void AddUserButton_Click(object sender, RoutedEventArgs e)
            {
                Frame.Navigate(typeof(Login));
            }
        }
    }
    
  • Hay algunos lugares en la aplicación donde quiere navegar a la página UserSelection . En MainPage.xaml.cs, debe ir a la página UserSelection en lugar de a la página Inicio de sesión . Mientras se encuentra en el evento cargado en MainPage, deberá cargar la lista de cuentas para que la página UserSelection pueda comprobar si hay alguna cuenta. Esto requerirá cambiar el Loaded método para que sea asincrónico y agregar también una instrucción using para el WindowsHelloLogin.Utils espacio de nombres.

    using WindowsHelloLogin.Utils;
    
    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Load the local account list before navigating to the UserSelection page
        await AccountHelper.LoadAccountListAsync();
        Frame.Navigate(typeof(UserSelection));
    }
    
  • A continuación, la aplicación tendrá que ir a la página UserSelection desde la página principal . En ambos Click eventos, debe volver a la página UserSelection .

    private void Button_Restart_Click(object sender, RoutedEventArgs e)
    {
        Frame.Navigate(typeof(UserSelection));
    }
    
    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Windows Hello
        WindowsHelloHelper.RemoveWindowsHelloAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and re-save the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine($"User {_activeAccount.Username} deleted.");
    
        // Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • En la página Inicio de sesión, necesita código para iniciar sesión en la cuenta seleccionada en la lista de la página UserSelection . En el OnNavigatedTo evento, almacene la cuenta pasada durante la navegación. Empiece agregando una nueva variable privada que identificará si la cuenta es una cuenta existente. A continuación, controle el OnNavigatedTo evento.

    namespace WindowsHelloLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
            private bool _isExistingAccount;
    
            public Login()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// Function called when this frame is navigated to.
            /// Checks to see if Windows Hello is available and if an account was passed in.
            /// If an account was passed in set the "_isExistingAccount" flag to true and set the _account.
            /// </summary>
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check Windows Hello is set up and available on this machine
                if (await WindowsHelloHelper.WindowsHelloAvailableCheckAsync())
                {
                    if (e.Parameter != null)
                    {
                        _isExistingAccount = true;
                        // Set the account to the existing account being passed in
                        _account = (Account)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        await SignInWindowsHelloAsync();
                    }
                }
                else
                {
                    // Windows Hello is not set up, so inform the user
                    WindowsHelloStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    WindowsHelloStatusText.Text = $"Windows Hello is not set up!{Environment.NewLine}Please go to Windows Settings and set up a PIN to use it.";
                    LoginButton.IsEnabled = false;
                }
            }
        }
    }
    
  • El método SignInWindowsHelloAsync deberá actualizarse para iniciar sesión en la cuenta seleccionada. WindowsHelloHelper necesitará otro método para abrir la cuenta con Windows Hello, ya que la cuenta ya tiene una clave de cuenta creada para ella. Implemente el nuevo método en WindowsHelloHelper.cs para iniciar sesión en un usuario existente con Windows Hello. Para obtener información sobre cada parte del código, lea los comentarios del código.

    /// <summary>
    /// Attempts to sign a message using the account key on the system for the accountId passed.
    /// </summary>
    /// <returns>Boolean representing if creating the Windows Hello authentication message succeeded</returns>
    public static async Task<bool> GetWindowsHelloAuthenticationMessageAsync(Account account)
    {
        KeyCredentialRetrievalResult openKeyResult = await KeyCredentialManager.OpenAsync(account.Username);
        // Calling OpenAsync will allow the user access to what is available in the app and will not require user credentials again.
        // If you wanted to force the user to sign in again you can use the following:
        // var consentResult = await Windows.Security.Credentials.UI.UserConsentVerifier.RequestVerificationAsync(account.Username);
        // This will ask for the either the password of the currently signed in Microsoft Account or the PIN used for Windows Hello.
    
        if (openKeyResult.Status == KeyCredentialStatus.Success)
        {
            // If OpenAsync has succeeded, the next thing to think about is whether the client application requires access to backend services.
            // If it does here you would request a challenge from the server. The client would sign this challenge and the server
            // would check the signed challenge. If it is correct, it would allow the user access to the backend.
            // You would likely make a new method called RequestSignAsync to handle all this.
            // For example, RequestSignAsync(openKeyResult);
            // Refer to the second Windows Hello sample for information on how to do this.
    
            // For this sample, there is not concept of a server implemented so just return true.
            return true;
        }
        else if (openKeyResult.Status == KeyCredentialStatus.NotFound)
        {
            // If the account is not found at this stage. It could be one of two errors. 
            // 1. Windows Hello has been disabled
            // 2. Windows Hello has been disabled and re-enabled cause the Windows Hello Key to change.
            // Calling CreateWindowsHelloKeyAsync and passing through the account will attempt to replace the existing Windows Hello Key for that account.
            // If the error really is that Windows Hello is disabled then the CreateWindowsHelloKeyAsync method will output that error.
            if (await CreateWindowsHelloKeyAsync(account.Username))
            {
                // If the Hello Key was again successfully created, Windows Hello has just been reset.
                // Now that the Hello Key has been reset for the account retry sign in.
                return await GetWindowsHelloAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Windows Hello right now, try again later
        return false;
    }
    
  • Actualice el método SignInWindowsHelloAsync en Login.xaml.cs para controlar la cuenta existente. Esto usará el nuevo método en el WindowsHelloHelper.cs. Si la cuenta se ha iniciado sesión correctamente y el usuario ha navegado a la página principal .

    private async Task SignInWindowsHelloAsync()
    {
        if (_isExistingAccount)
        {
            if (await WindowsHelloHelper.GetWindowsHelloAuthenticationMessageAsync(_account))
            {
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else if (AccountHelper.ValidateAccountCredentials(UsernameTextBox.Text))
        {
            //Create and add a new local account
            _account = AccountHelper.AddAccount(UsernameTextBox.Text);
            Debug.WriteLine("Successfully signed in with traditional credentials and created local account instance!");
    
            if (await WindowsHelloHelper.CreateWindowsHelloKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Windows Hello!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • Compile y ejecute la aplicación. Inicie sesión con "sampleUsername". Escriba el PIN y, si se ha realizado correctamente, se le dirigirá a la página principal . Haga clic de nuevo en la lista de usuarios. Ahora debería ver un usuario en la lista. Si haces clic en esto, WindowsHello te permite volver a iniciar sesión sin tener que volver a escribir ninguna contraseña etcetera.

    Captura de pantalla de la lista de usuarios de selección de Windows Hello

Ejercicio 3: Registro de un nuevo usuario de Windows Hello

En este ejercicio, creará una nueva página que puede crear una nueva cuenta con Windows Hello. Esto funciona de forma similar a cómo funciona la página Inicio de sesión . La página Inicio de sesión se implementa para un usuario existente que está migrando para usar Windows Hello. Una página WindowsHelloRegister creará el registro de Windows Hello para un nuevo usuario.

  • En la carpeta Vistas , cree una página en blanco denominada "WindowsHelloRegister.xaml". En el complemento XAML, agregue lo siguiente para configurar la interfaz de usuario. La interfaz de esta página es similar a la página Inicio de sesión.

    <Grid>
      <StackPanel>
        <TextBlock x:Name="Title" Text="Register New Windows Hello User" FontSize="24" Margin="4" TextAlignment="Center"/>
    
        <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
    
        <TextBlock Text="Enter your new username below" Margin="0,0,0,20"
                   TextWrapping="Wrap" Width="300"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    
        <TextBox x:Name="UsernameTextBox" Margin="4" Width="250"/>
    
        <Button x:Name="RegisterButton" Content="Register" Background="DodgerBlue" Foreground="White"
                Click="RegisterButton_Click_Async" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
    
        <Border x:Name="WindowsHelloStatus" Background="#22B14C" Margin="4" Height="100">
          <TextBlock x:Name="WindowsHelloStatusText" Text="Windows Hello is ready to use!" FontSize="20"
                     Margin="4" TextAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </StackPanel>
    </Grid>
    
  • En el WindowsHelloRegister.xaml.cs archivo de código subyacente, implemente una variable privada Account y un Click evento para el botón registrar. Esto agregará una nueva cuenta local y creará una clave de Windows Hello.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml;
    using WindowsHelloLogin.Models;
    using WindowsHelloLogin.Utils;
    
    namespace WindowsHelloLogin.Views
    {
        public sealed partial class WindowsHelloRegister : Page
        {
            private Account _account;
    
            public WindowsHelloRegister()
            {
                InitializeComponent();
            }
    
            private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
    
                // In the real world, you would validate the entered credentials and information before 
                // allowing a user to register a new account. 
                // For this sample, we'll skip that step and just register an account if the username is not null.
    
                if (!string.IsNullOrEmpty(UsernameTextBox.Text))
                {
                    // Register a new account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    // Register new account with Windows Hello
                    await WindowsHelloHelper.CreateWindowsHelloKeyAsync(_account.Username);
                    // Navigate to the Welcome page. 
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    ErrorMessage.Text = "Please enter a username";
                }
            }
        }
    }
    
  • Debe ir a esta página desde la página Inicio de sesión cuando se haga clic en el registro.

    private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        ErrorMessage.Text = "";
        Frame.Navigate(typeof(WindowsHelloRegister));
    }
    
  • Compile y ejecute la aplicación. Intente registrar un nuevo usuario. A continuación, vuelva a la lista de usuarios y compruebe que puede seleccionar ese usuario e inicio de sesión.

    Captura de pantalla de la página Registrar nuevo usuario de Windows Hello

En este laboratorio, ha aprendido las aptitudes esenciales necesarias para usar la nueva API de Windows Hello para autenticar a los usuarios existentes y crear cuentas para nuevos usuarios. Con este nuevo conocimiento, puede empezar a quitar la necesidad de que los usuarios recuerden las contraseñas de la aplicación, pero siguen confiando en que las aplicaciones permanecen protegidas por la autenticación de usuario. Windows usa la nueva tecnología de autenticación de Windows Hello para admitir sus opciones de inicio de sesión biométricos.