Erstellen einer Windows Hello-Anmelde-App

Dies ist der erste Teil einer vollständigen exemplarischen Vorgehensweise zum Erstellen einer verpackten Windows-App, die Windows Hello als Alternative zu herkömmlichen Benutzername- und Kennwortauthentifizierungssystemen verwendet. In diesem Fall ist die App eine WinUI-App, aber derselbe Ansatz kann mit jeder verpackten Windows-App verwendet werden, einschließlich WPF- und Windows Forms-Apps. Die App verwendet einen Benutzernamen für die Anmeldung und erstellt für jedes Konto einen Hello-Schlüssel. Diese Konten werden durch die PIN geschützt, die in den Windows-Einstellungen für die Konfiguration von Windows Hello eingerichtet ist.

Diese exemplarische Vorgehensweise ist in zwei Teile unterteilt: Erstellen der App und Verbinden des Back-End-Diensts. Wenn Sie mit diesem Artikel fertig sind, fahren Sie mit Teil 2: Windows Hello-Anmeldedienst fort.

Bevor Sie beginnen, sollten Sie die Windows Hello-Übersicht lesen, um ein allgemeines Verständnis der Funktionsweise von Windows Hello zu finden.

Erste Schritte

Um dieses Projekt zu erstellen, benötigen Sie einige Erfahrungen mit C# und XAML. Außerdem müssen Sie Visual Studio 2022 auf einem Windows 10- oder Windows 11-Computer verwenden. Ausführliche Anweisungen zum Einrichten Ihrer Entwicklungsumgebung finden Sie unter "Erste Schritte mit WinUI ".

  • Klicken Sie in Visual Studio auf Datei>Neu>Projekt.
  • Wählen Sie in den Dropdownfiltern des Dialogfelds "Neues Projekt" die Option "C#/C++", "Windows" bzw. "WinUI" aus.
  • Wählen Sie "Leere App", "Verpackt" (WinUI 3 in Desktop) aus, und nennen Sie Ihre Anwendung "WindowsHelloLogin".
  • Erstellen und Ausführen der neuen Anwendung (F5) sollte ein leeres Fenster auf dem Bildschirm angezeigt werden. Schließen Sie die Anwendung.

Screenshot der neuen Windows Hello-Anmelde-App, die zum ersten Mal ausgeführt wird

Übung 1: Anmelden mit Windows Hello

In dieser Übung erfahren Sie, wie Sie überprüfen, ob Windows Hello auf dem Computer eingerichtet ist und wie Sie sich mit Windows Hello bei einem Konto anmelden.

  • Erstellen Sie im neuen Projekt einen neuen Ordner in der Projektmappe namens "Views". Dieser Ordner enthält die Seiten, zu denen in diesem Beispiel navigiert wird. Klicken Sie mit der rechten Maustaste in Projektmappen-Explorer auf das Projekt, wählen Sie "Neuen Ordner hinzufügen>" aus, und benennen Sie den Ordner in "Ansichten" um.

    Screenshot des Hinzufügens eines neuen Ordners mit dem Namen

  • Öffnen Sie "MainWindow.xaml", und ersetzen Sie den Window Inhalt durch ein leeres StackPanel Steuerelement.Grid Wir implementieren die Seitennavigation und navigieren zu einer neuen Seite, wenn das MainWindow geladen wird, daher benötigen wir keinen Inhalt im MainWindow.

  • Fügen Sie im XAML-Code eine Title Eigenschaft zu "MainWindow " hinzu. Das Attribut sollte wie folgt aussehen: Title="Windows Hello Login".

  • Entfernen Sie den myButton_Click Ereignishandler aus MainWindow.xaml.cs, um Kompilierungsfehler zu vermeiden. Dieser Ereignishandler ist für dieses Beispiel nicht erforderlich.

  • Klicken Sie mit der rechten Maustaste auf den neuen Ordner "Ansichten", wählen Sie "Neues Element hinzufügen>" und dann die Vorlage "Leere Seite" aus. Nennen Sie diese Seite "MainPage.xaml".

    Screenshot des Hinzufügens einer neuen leeren Seite zum Windows Hello-Anmeldeprojekt

  • Öffnen Sie die App.xaml.cs Datei, und aktualisieren Sie den OnLaunched-Handler , um die Seitennavigation für die App zu implementieren. Außerdem müssen Sie eine RootFrame_NavigationFailed-Handlermethode hinzufügen, um fehler zu behandeln, die beim Laden von Seiten auftreten.

    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}");
    }
    
  • Außerdem müssen Sie oben in der App.xaml.cs-Datei vier using-Anweisungen hinzufügen, um Kompilierungsfehler im Code zu beheben.

    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using WindowsHelloLogin.Views;
    
  • Klicken Sie mit der rechten Maustaste auf den neuen Ordner "Ansichten", wählen Sie "Neues Element hinzufügen>" und dann die Vorlage "Leere Seite" aus. Nennen Sie diese Seite "Login.xaml".

  • Um die Benutzeroberfläche für die neue Anmeldeseite zu definieren, fügen Sie den folgenden XAML-Code hinzu. Dieser XAML-Code definiert eine StackPanel Ausrichtung der folgenden untergeordneten Elemente:

    • Ein TextBlock Titel, der einen Titel enthält.

    • A TextBlock für Fehlermeldungen.

    • A TextBox für den Benutzernamen, der eingegeben werden soll.

    • A Button , um zu einer Registerseite zu navigieren.

    • A TextBlock , das den Status von Windows Hello enthalten soll.

    • Eine TextBlock , um die Anmeldeseite zu erläutern, da noch keine Back-End- oder konfigurierten Benutzer vorhanden sind.

      <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>
      
  • Einige Methoden müssen der CodeBehind-Datei hinzugefügt werden, um die Lösungserstellung abzurufen. Drücken Sie F7, oder verwenden Sie die Projektmappen-Explorer, um die Login.xaml.cs Datei zu bearbeiten. Fügen Sie die folgenden beiden Ereignismethoden hinzu, um die Anmelde- und Registerereignisse zu behandeln. Vorerst legen diese Methoden die ErrorMessage.Text Zeichenfolge auf eine leere Zeichenfolge fest. Achten Sie darauf, die folgenden using-Anweisungen einzuschließen. Sie werden für die nächsten Schritte benötigt.

    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 = "";
            }
        }
    }
    
  • Um die Anmeldeseite zu rendern, bearbeiten Sie den MainPage-Code, um zur Anmeldeseite zu navigieren, wenn die MainPage geladen wird. Öffnen Sie die Datei „MainPage.xaml.cs“. Doppelklicken Sie in Projektmappen-Explorer auf MainPage.xaml.cs. Wenn Sie diesen Pfeil nicht finden können, klicken Sie auf den kleinen Pfeil neben "MainPage.xaml", um die CodeBehind-Datei anzuzeigen. Erstellen Sie eine Loaded-Ereignishandlermethode , die zur Anmeldeseite navigiert.

    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));
            }
        }
    }
    
  • Auf der Anmeldeseite müssen Sie das OnNavigatedTo Ereignis behandeln, um zu überprüfen, ob Windows Hello auf dem aktuellen Computer verfügbar ist. Implementieren Sie in Login.xaml.cs den folgenden Code. Sie werden feststellen, dass das WindowsHelloHelper-Objekt angibt, dass ein Fehler vorliegt. Das liegt daran, dass wir diese Hilfsklasse noch nicht erstellt haben.

    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;
            }
        }
    }
    
  • Um die WindowsHelloHelper-Klasse zu erstellen, klicken Sie mit der rechten Maustaste auf das WindowsHelloLogin-Projekt, und klicken Sie auf "Neuen Ordner hinzufügen">. Nennen Sie diesen Ordner "Utils".

  • Klicken Sie mit der rechten Maustaste auf den Ordner "Utils", und wählen Sie "Klasse hinzufügen">aus. Nennen Sie diese neue Klasse "WindowsHelloHelper.cs".

    Screenshot des Erstellens der Windows Hello-Anmeldehilfsklasse

  • Ändern Sie den Gültigkeitsbereich der WindowsHelloHelper-Klasse , public staticund fügen Sie dann die folgende Methode hinzu, um den Benutzer darüber zu informieren, ob Windows Hello verwendet werden kann oder nicht. Sie müssen die erforderlichen Namespaces hinzufügen.

    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;
            }
        }
    }
    
  • Fügen Sie in Login.xaml.cs einen Verweis auf den WindowsHelloLogin.Utils Namespace hinzu. Dadurch wird der Fehler in der OnNavigatedTo Methode behoben.

    using WindowsHelloLogin.Utils;
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Sie werden zur Anmeldeseite navigiert, und das Windows Hello-Banner gibt An, ob Windows Hello verwendet werden kann. Sie sollten entweder das grüne oder blaue Banner sehen, das den Windows Hello-Status auf Ihrem Computer angibt.

    Screenshot des Windows Hello-Anmeldebildschirms mit einem bereiten Status

  • Als Nächstes müssen Sie die Logik für die Anmeldung erstellen. Erstellen Sie einen neuen Ordner im Projekt mit dem Namen "Models".

  • Erstellen Sie im Ordner "Models " eine neue Klasse namens "Account.cs". Diese Klasse fungiert als Kontomodell. Da es sich um ein Beispielprojekt handelt, enthält es nur einen Benutzernamen. Ändern Sie den Klassenbereich in public die Eigenschaft, und fügen Sie sie Username hinzu.

    namespace WindowsHelloLogin.Models
    {
        public class Account
        {
            public string Username { get; set; }
        }
    }
    
  • Die App benötigt eine Möglichkeit zum Behandeln von Konten. Für diese praktische Übung, da kein Server oder keine Datenbank vorhanden ist, werden eine Liste der Benutzer lokal gespeichert und geladen. Klicken Sie mit der rechten Maustaste auf den Ordner "Utils ", und fügen Sie eine neue Klasse namens "AccountHelper.cs" hinzu. Ändern Sie den Klassenbereich so, dass er sein soll public static. AccountHelper ist eine statische Klasse, die alle erforderlichen Methoden enthält, um die Liste der Konten lokal zu speichern und zu laden. Das Speichern und Laden funktioniert mithilfe eines XmlSerializers. Sie müssen sich auch an die Datei erinnern, die gespeichert wurde und wo Sie sie gespeichert haben.

    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>;
            }
        }
    }
    
  • Implementieren Sie als Nächstes eine Möglichkeit zum Hinzufügen und Entfernen eines Kontos aus der lokalen Liste der Konten. Diese Aktionen speichern die Liste jeweils. Die endgültige Methode, die Sie für dieses praktische Labor benötigen, ist eine Validierungsmethode. Da kein Autorisierungsserver oder keine Benutzerdatenbank vorhanden ist, wird dies für einen einzelnen Benutzer überprüft, der hartcodiert ist. Diese Methoden sollten der AccountHelper-Klasse hinzugefügt werden.

    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;
    }
    
  • Als Nächstes müssen Sie eine Anmeldeanforderung des Benutzers behandeln. Erstellen Sie in Login.xaml.cs eine neue private Variable, die die aktuelle Kontoanmeldung enthält. Fügen Sie dann eine neue Methode namens SignInWindowsHelloAsync hinzu. Dadurch werden die Kontoanmeldeinformationen mithilfe der AccountHelper.ValidateAccountCredentials-Methode überprüft. Diese Methode gibt einen booleschen Wert zurück, wenn der eingegebene Benutzername mit dem hartcodierten Zeichenfolgenwert übereinstimmt, den Sie im vorherigen Schritt konfiguriert haben. Der hartcodierte Wert für dieses Beispiel lautet "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";
                }
            }
        }
    }
    
  • Möglicherweise haben Sie den kommentierten Code bemerkt, der auf eine Methode in WindowsHelloHelper verweist. Fügen Sie in WindowsHelloHelper.cs eine neue Methode namens CreateWindowsHelloKeyAsync hinzu. Diese Methode verwendet die Windows Hello-API im KeyCredentialManager. Durch Aufrufen von RequestCreateAsync wird ein Windows Hello-Schlüssel erstellt, der für die accountId und den lokalen Computer spezifisch ist. Beachten Sie die Kommentare in der Switch-Anweisung, wenn Sie dies in einem realen Szenario implementieren möchten.

    /// <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;
    }
    
  • Nachdem Sie die CreateWindowsHelloKeyAsync-Methode erstellt haben, kehren Sie zur Login.xaml.cs Datei zurück, und entfernen Sie die Kommentare im Code innerhalb der SignInWindowsHelloAsync-Methode.

    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";
        }
    }
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Sie gelangen zur Anmeldeseite. Geben Sie den Benutzernamen als "sampleUsername" ein, und klicken Sie auf "Login". Sie werden mit einer Windows Hello-Eingabeaufforderung aufgefordert, Ihre PIN einzugeben. Nach der korrekten Eingabe der PIN kann die CreateWindowsHelloKeyAsync-Methode einen Windows Hello-Schlüssel erstellen. Überwachen Sie die Ausgabefenster, um festzustellen, ob die Nachrichten angezeigt werden, die den Erfolg angeben.

    Screenshot der Windows Hello-Anmelde-Pin-Eingabeaufforderung

Übung 2: Willkommens- und Benutzerauswahlseiten

In dieser Übung fahren Sie mit der vorherigen Übung fort. Wenn sich ein Benutzer erfolgreich anmeldet, sollte er zu einer Willkommensseite weitergeleitet werden, auf der er sich abmelden oder sein Konto löschen kann. Wenn Windows Hello einen Schlüssel für jeden Computer erstellt, kann ein Benutzerauswahlbildschirm erstellt werden, der alle Benutzer anzeigt, die auf diesem Computer angemeldet sind. Ein Benutzer kann dann eines dieser Konten auswählen und direkt zur Willkommensseite wechseln, ohne dass ein Kennwort erneut eingegeben werden muss, da er sich bereits für den Zugriff auf den Computer authentifiziert hat.

  • Fügen Sie im Ordner "Ansichten " eine neue leere Seite namens "Welcome.xaml" hinzu. Fügen Sie den folgenden XAML-Code hinzu, um die Benutzeroberfläche für die Seite abzuschließen. Dadurch wird ein Titel, der angemeldete Benutzername und zwei Schaltflächen angezeigt. Eine der Schaltflächen navigiert zurück zu einer Benutzerliste (die Sie später erstellen), und die andere Schaltfläche behandelt das Vergessen dieses Benutzers.

    <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>
    
  • Fügen Sie in der Welcome.xaml.cs CodeBehind-Datei eine neue private Variable hinzu, die das angemeldete Konto enthält. Sie müssen eine Methode implementieren, um das OnNavigateTo Ereignis außer Kraft zu setzen. Dadurch wird das An die Willkommensseite übergebene Konto gespeichert. Außerdem müssen Sie das Click Ereignis für die beiden im XAML-Code definierten Schaltflächen implementieren. Sie müssen using-Anweisungen für die WindowsHelloLogin.Models Und-Namespaces WindowsHelloLogin.Utils hinzufügen.

    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.");
            }
        }
    }
    
  • Möglicherweise haben Sie eine Zeilenkommentar im Button_Forget_User_Click Ereignishandler bemerkt. Das Konto wird aus Ihrer lokalen Liste entfernt, aber derzeit gibt es keine Möglichkeit, aus Windows Hello entfernt zu werden. Sie müssen eine neue Methode in WindowsHelloHelper.cs implementieren, die das Entfernen eines Windows Hello-Benutzers behandelt. Diese Methode verwendet andere Windows Hello-APIs, um das Konto zu öffnen und zu löschen. Wenn Sie ein Konto löschen, sollte in der realen Welt der Server oder die Datenbank benachrichtigt werden, damit die Benutzerdatenbank gültig bleibt. Sie benötigen eine using-Anweisung, die auf den WindowsHelloLogin.Models Namespace verweist.

    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);
    }
    
  • Entfernen Sie in Welcome.xaml.cs die Kommentarzeile, die RemoveWindowsHelloAccountAsync aufruft.

    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.");
    }
    
  • In der SignInWindowsHelloAsync-Methode (in Login.xaml.cs) sollte nach erfolgreicher Ausführung von CreateWindowsHelloKeyAsync zur Willkommensseite navigiert und das Konto übergeben werden.

    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";
        }
    }
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Melden Sie sich mit "sampleUsername" an, und klicken Sie auf " Anmelden". Geben Sie Ihre PIN ein, und wenn erfolgreich, sollten Sie zur Willkommensseite navigieren. Versuchen Sie, auf das Fenster "Benutzer vergessen" zu klicken und das Ausgabefenster von Visual Studio zu überwachen, um festzustellen, ob der Benutzer gelöscht wurde. Beachten Sie, dass Sie, wenn der Benutzer gelöscht wird, auf der Willkommensseite verbleiben. Sie müssen eine Benutzerauswahlseite erstellen, zu der die App navigieren kann.

    Screenshot des Windows Hello-Willkommensbildschirms

  • Erstellen Sie im Ordner "Views " eine neue leere Seite mit dem Namen "UserSelection.xaml", und fügen Sie den folgenden XAML-Code hinzu, um die Benutzeroberfläche zu definieren. Diese Seite enthält eine ListView , die alle Benutzer in der Liste der lokalen Konten anzeigt, und eine Button , die zur Anmeldeseite navigiert, damit der Benutzer ein weiteres Konto hinzufügen kann.

    <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>
    
  • Implementieren Sie in UserSelection.xaml.cs die Loaded Methode, die zur Anmeldeseite navigiert, wenn keine Konten in der lokalen Liste vorhanden sind. Implementieren Sie außerdem das SelectionChanged Ereignis für das ListView und ein Click Ereignis für das 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));
            }
        }
    }
    
  • Es gibt einige Stellen in der App, an denen Sie zur UserSelection-Seite navigieren möchten. In MainPage.xaml.cs sollten Sie anstelle der Anmeldeseite zur UserSelection-Seite navigieren. Während Sie sich im geladenen Ereignis in MainPage befinden, müssen Sie die Kontenliste laden, damit die UserSelection-Seite überprüfen kann, ob Konten vorhanden sind. Dies erfordert, dass die Loaded Methode asynchron ist und außerdem eine using-Anweisung für den WindowsHelloLogin.Utils Namespace hinzugefügt wird.

    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));
    }
    
  • Als Nächstes muss die App von der Willkommensseite zur UserSelection-Seite navigieren. In beiden Click Ereignissen sollten Sie zurück zur UserSelection-Seite navigieren.

    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));
    }
    
  • Auf der Anmeldeseite benötigen Sie Code, um sich bei dem Konto anzumelden, das in der Liste auf der UserSelection-Seite ausgewählt ist. Speichern Sie das Konto, das während der OnNavigatedTo Navigation übergeben wurde, im Ereignis. Fügen Sie zunächst eine neue private Variable hinzu, die identifiziert, ob es sich bei dem Konto um ein vorhandenes Konto handelt. Behandeln Sie dann das OnNavigatedTo Ereignis.

    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;
                }
            }
        }
    }
    
  • Die SignInWindowsHelloAsync-Methode muss aktualisiert werden, um sich beim ausgewählten Konto anzumelden. WindowsHelloHelper benötigt eine weitere Methode, um das Konto mit Windows Hello zu öffnen, da das Konto bereits einen Kontoschlüssel dafür erstellt hat. Implementieren Sie die neue Methode in WindowsHelloHelper.cs, um einen vorhandenen Benutzer mit Windows Hello anzumelden. Informationen zu jedem Teil des Codes finden Sie in den Codekommentaren.

    /// <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;
    }
    
  • Aktualisieren Sie die SignInWindowsHelloAsync-Methode in Login.xaml.cs, um das vorhandene Konto zu verarbeiten. Dadurch wird die neue Methode im WindowsHelloHelper.cs verwendet. Bei erfolgreicher Ausführung wird das Konto angemeldet und der Benutzer zur Willkommensseite navigiert.

    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";
        }
    }
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Melden Sie sich mit "sampleUsername" an. Geben Sie Ihre PIN ein, und wenn erfolgreich, werden Sie zur Willkommensseite navigiert. Klicken Sie zurück zur Benutzerliste. Nun sollte ein Benutzer in der Liste angezeigt werden. Wenn Sie auf diese Option klicken, können Sie sich von WindowsHello wieder anmelden, ohne kennwörter erneut eingeben zu müssen usw.

    Screenshot der Windows Hello-Liste

Übung 3: Registrieren eines neuen Windows Hello-Benutzers

In dieser Übung erstellen Sie eine neue Seite, die ein neues Konto mit Windows Hello erstellen kann. Dies funktioniert ähnlich wie die Anmeldeseite . Die Anmeldeseite wird für einen vorhandenen Benutzer implementiert, der zur Verwendung von Windows Hello migriert wird. Eine WindowsHelloRegister-Seite erstellt die Windows Hello-Registrierung für einen neuen Benutzer.

  • Erstellen Sie im Ordner "Ansichten " eine neue leere Seite mit dem Namen "WindowsHelloRegister.xaml". Fügen Sie im XAML-Code Folgendes hinzu, um die Benutzeroberfläche einzurichten. Die Benutzeroberfläche auf dieser Seite ähnelt der Anmeldeseite .

    <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>
    
  • Implementieren Sie in der WindowsHelloRegister.xaml.cs CodeBehind-Datei eine private Account Variable und ein Click Ereignis für die Registrierungsschaltfläche. Dadurch wird ein neues lokales Konto hinzugefügt und ein Windows Hello-Schlüssel erstellt.

    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";
                }
            }
        }
    }
    
  • Sie müssen auf der Anmeldeseite von der Anmeldeseite zu dieser Seite navigieren, wenn auf die Registrierung geklickt wird.

    private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        ErrorMessage.Text = "";
        Frame.Navigate(typeof(WindowsHelloRegister));
    }
    
  • Erstellen Sie die Anwendung, und führen Sie sie aus. Versuchen Sie, einen neuen Benutzer zu registrieren. Kehren Sie dann zur Benutzerliste zurück, und überprüfen Sie, ob Sie diesen Benutzer auswählen und sich anmelden können.

    Screenshot der Windows Hello-Seite

In dieser Übung haben Sie die wesentlichen Fähigkeiten gelernt, die erforderlich sind, um die neue Windows Hello-API zum Authentifizieren vorhandener Benutzer zu verwenden und Konten für neue Benutzer zu erstellen. Mit diesem neuen Wissen können Sie damit beginnen, die Notwendigkeit zu entfernen, dass Benutzer Kennwörter für Ihre Anwendung speichern müssen, und dennoch sicher bleiben, dass Ihre Anwendungen durch die Benutzerauthentifizierung geschützt bleiben. Windows verwendet die neue Authentifizierungstechnologie von Windows Hello, um ihre biometrischen Anmeldeoptionen zu unterstützen.