次の方法で共有


Windows Hello ログイン アプリの作成

これは、従来のユーザー名とパスワード認証システムの代わりに Windows Hello を使用する Windows 10 および Windows 11 UWP (ユニバーサル Windows プラットフォーム) アプリを作成する方法に関する完全なチュートリアルのパート 1 です。 このアプリでは、サインインにユーザー名を使用し、アカウントごとに Hello キーを作成します。 これらのアカウントは、Windows Hello の構成に関する Windows 設定で設定されている PIN によって保護されます。

このチュートリアルは、アプリのビルドとバックエンド サービスの接続という 2 つの部分に分かれています。 この記事が完了したら、パート 2: Windows Hello ログイン サービスに進みます。

作業を開始する前に、Windows Hello のしくみの一般的な理解 Windows Hello の概要を確認する必要があります。

作業の開始

このプロジェクトをビルドするには、C# と XAML のエクスペリエンスが必要です。 また、Windows 10 または Windows 11 コンピューターで Visual Studio 2015 (Community Edition 以降)、またはそれ以降のリリースの Visual Studio を使用する必要もあります。 最低限必要なバージョンは Visual Studio 2015 ですが、開発者とセキュリティの最新の更新プログラムのために、最新バージョンの Visual Studio を使用することをお勧めします。

  • Visual Studio を開き、[ファイル] > [新規] > [プロジェクト] を選択します。
  • [新しいプロジェクト] ウィンドウが開きます。 [テンプレート] > [Visual C#] の順に移動します。
  • [空のアプリ (ユニバーサル Windows)] を選択し、アプリケーションに "PassportLogin" という名前を付けます。
  • 新しいアプリケーションをビルドして実行すると (F5)、画面に空白のウィンドウが表示されます。 アプリケーションを閉じます。

Windows Hello の新しいプロジェクト

演習 1: Microsoft Passport を使用してログインする

この演習では、Windows Hello がコンピューターにセットアップされているかどうかを確認する方法と、Windows Hello を使用してアカウントにサインインする方法について説明します。

  • 新しいプロジェクトで、"Views" という名前の新しいフォルダーをソリューションに作成します。 このフォルダーには、このサンプルで移動するページが含まれます。 ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] > [新しいフォルダー] の順に選択して、フォルダー名を「Views」に変更します。

    Windows Hello のフォルダーの追加

  • 新しい Views フォルダーを右クリックし、[追加] > [新しい項目] の順に選択して、[空白のページ] を選択します。 このページに "Login.xaml" という名前を付けます。

    Windows Hello の空白ページの追加

  • 新しいログイン ページのユーザー インターフェイスを定義するには、次の XAML を追加します。 この XAML は、次の子を配置する StackPanel を定義します。

    • タイトルを含む TextBlock。
    • エラー メッセージの TextBlock。
    • 入力するユーザー名のテキスト ボックス。
    • 登録ページに移動するボタン。
    • Windows Hello の状態を格納する TextBlock。
    • バックエンドまたは構成済みのユーザーが存在しないため、[ログイン] ページを説明する TextBlock。
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <TextBlock Text="Login" FontSize="36" Margin="4" TextAlignment="Center"/>
        <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>
        <TextBlock Text="Enter your 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="PassportSignInButton" Content="Login" Background="DodgerBlue" Foreground="White"
            Click="PassportSignInButton_Click" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
        <TextBlock Text="Don't have an account?"
                    TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
        <TextBlock x:Name="RegisterButtonTextBlock" Text="Register now"
                   PointerPressed="RegisterButtonTextBlock_OnPointerPressed"
                   Foreground="DodgerBlue"
                   TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
        <Border x:Name="PassportStatus" Background="#22B14C"
                   Margin="0,20" Height="100" >
          <TextBlock x:Name="PassportStatusText" Text="Microsoft Passport is ready to use!"
                 Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
        </Border>
        <TextBlock x:Name="LoginExplaination" FontSize="24" TextAlignment="Center" TextWrapping="Wrap" 
            Text="Please Note: To demonstrate a login, validation will only occur using the default username 'sampleUsername'"/>
      </StackPanel>
    </Grid>
    
  • ソリューションのビルドを取得するには、分離コードにいくつかのメソッドを追加する必要があります。 F7 キーを押すか、ソリューション エクスプローラーを使用してLogin.xaml.csに移動します。 Login イベントと Register イベントを処理する次の 2 つのイベント メソッドを追加します。 現時点では、これらのメソッドは ErrorMessage.Text を空の文字列に設定します。

    namespace PassportLogin.Views
    {
        public sealed partial class Login : Page
        {
            public Login()
            {
                this.InitializeComponent();
            }
    
            private void PassportSignInButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
        }
    }
    
  • ログイン ページをレンダリングするには、MainPage が読み込まれたときにログイン ページに移動するように MainPage コードを編集します。 MainPage.xaml.cs ファイルを開きます。 ソリューション エクスプローラーで、MainPage.xaml.csをダブルクリックします。 見つからない場合は、MainPage.xaml の横にある小さな矢印をクリックして、分離コードを表示します。 ログイン ページに移動する読み込まれたイベント ハンドラー メソッドを作成します。 Views 名前空間への参照を追加する必要があります。

    using PassportLogin.Views;
    
    namespace PassportLogin
    {
        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));
            }
        }
    }
    
  • [ログイン] ページで、OnNavigatedTo イベントを処理して、このマシンで Windows Hello が使用可能かどうかを検証する必要があります。 Login.xaml.csでは、次のコードを実装します。 MicrosoftPassportHelper オブジェクトがエラーにフラグを設定していることがわかります。 これは、まだ実装していないためです。

    public sealed partial class Login : Page
    {
        public Login()
        {
            this.InitializeComponent();
        }
    
        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            // Check Microsoft Passport is setup and available on this machine
            if (await MicrosoftPassportHelper.MicrosoftPassportAvailableCheckAsync())
            {
            }
            else
            {
                // Microsoft Passport is not setup so inform the user
                PassportStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                PassportStatusText.Text = "Microsoft Passport is not setup!\n" + 
                    "Please go to Windows Settings and set up a PIN to use it.";
                PassportSignInButton.IsEnabled = false;
            }
        }
    }
    
  • MicrosoftPassportHelper クラスを作成するには、PassportLogin (ユニバーサル Windows) ソリューションを右クリックし、[追加] > [新しいフォルダー] の順にクリックします。 このフォルダーに Utils という名前を付けます。

    passport create ヘルパー クラス

  • Utils フォルダーを右クリックし、[追加] > [クラス] の順にクリックします。 このクラスに "MicrosoftPassportHelper.cs" という名前を付けます。

  • MicrosoftPassportHelper のクラス定義をパブリック静的に変更し、次のメソッドを追加して、Windows Hello を使用する準備ができているかどうかをユーザーに通知します。 必要な名前空間を追加する必要があります。

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using Windows.Security.Credentials;
    
    namespace PassportLogin.Utils
    {
        public static class MicrosoftPassportHelper
        {
            /// <summary>
            /// Checks to see if Passport is ready to be used.
            /// 
            /// Passport 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> MicrosoftPassportAvailableCheckAsync()
            {
                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("Microsoft Passport is not setup!\nPlease go to Windows Settings and set up a PIN to use it.");
                    return false;
                }
    
                return true;
            }
        }
    }
    
  • Login.xaml.csで、Utils 名前空間への参照を追加します。 これにより、OnNavigatedTo メソッドのエラーが解決されます。

    using PassportLogin.Utils;
    
  • アプリケーションをビルドして実行します (F5)。 ログイン ページに移動し、Hello を使用する準備ができているかどうかを示す Windows Hello バナーが表示されます。 コンピューターの Windows Hello の状態を示す緑色または青のバナーが表示されます。

    Windows Hello ログイン画面の準備完了

    Windows Hello ログイン画面がセットアップされていない

  • 次に、サインインのロジックを構築する必要があります。 "Models" という名前の新しいフォルダーを作成します。

  • Models フォルダーに、"Account.cs" という名前の新しいクラスを作成します。 このクラスは、アカウント モデルとして機能します。 これはサンプルであるため、ユーザー名のみが含まれます。 クラス定義を public に変更し、Username プロパティを追加します。

    namespace PassportLogin.Models
    {
        public class Account
        {
            public string Username { get; set; }
        }
    }
    
  • アカウントを処理する方法が必要です。 サーバーまたはデータベースがないため、このハンズオン ラボでは、ユーザーの一覧が保存され、ローカルに読み込まれます。 Utils フォルダーを右クリックし、"AccountHelper.cs" という名前の新しいクラスを追加します。 クラス定義をパブリック静的に変更します。 AccountHelper は、アカウントの一覧をローカルに保存して読み込むのに必要なすべてのメソッドを含む静的クラスです。 XmlSerializer を使用すると、保存と読み込みが機能します。 また、保存したファイルと保存した場所も記憶する必要があります。

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml.Serialization;
    using Windows.Storage;
    using PassportLogin.Models;
    
    namespace PassportLogin.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 = new List<Account>();
    
            /// <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()
            {
                XmlSerializer xmlizer = new XmlSerializer(typeof(List<Account>));
                StringWriter 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)
            {
                XmlSerializer xmlizer = new XmlSerializer(typeof(List<Account>));
                TextReader textreader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(listAsXml)));
    
                return AccountList = (xmlizer.Deserialize(textreader)) as List<Account>;
            }
        }
    }
    
  • 次に、アカウントのローカル リストからアカウントを追加および削除する方法を実装します。 これらのアクションによって、それぞれ一覧が保存されます。 このハンズオン ラボに必要な最後の方法は、検証方法です。 認証サーバーまたはユーザーのデータベースがないため、ハードコーディングされた 1 人のユーザーに対して検証されます。 これらのメソッドは AccountHelper クラスに追加する必要があります。

    public static Account AddAccount(string username)
            {
                // Create a new account with the username
                Account 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.
                // For this tutorial however we will just have an existing sample user that is just "sampleUsername"
                // If the username is null or does not match "sampleUsername" it will fail validation. In which case the user should register a new passport user
    
                if (string.IsNullOrEmpty(username))
                {
                    return false;
                }
    
                if (!string.Equals(username, "sampleUsername"))
                {
                    return false;
                }
    
                return true;
            }
    
  • 次に、ユーザーからのサインイン要求を処理する必要があります。 Login.xaml.csで、現在のアカウントのログインを保持する新しいプライベート変数を作成します。 次に、新しいメソッド呼び出し SignInPassport を追加します。 これにより、AccountHelper.ValidateAccountCredentials メソッドを使用してアカウント資格情報が検証されます。 入力したユーザー名が、前の手順で設定したハード コーディングされた文字列値と同じ場合、このメソッドはブール値を返します。 このサンプルのハード コーディングされた値は "sampleUsername" です。

    using PassportLogin.Models;
    using PassportLogin.Utils;
    using System.Diagnostics;
    
    namespace PassportLogin.Views
    {
        public sealed partial class Login : Page
        {
            private Account _account;
    
            public Login()
            {
                this.InitializeComponent();
            }
    
            protected override async void OnNavigatedTo(NavigationEventArgs e)
            {
                // Check Microsoft Passport is setup and available on this machine
                if (await MicrosoftPassportHelper.MicrosoftPassportAvailableCheckAsync())
                {
                }
                else
                {
                    // Microsoft Passport is not setup so inform the user
                    PassportStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    PassportStatusText.Text = "Microsoft Passport is not setup!\nPlease go to Windows Settings and set up a PIN to use it.";
                    PassportSignInButton.IsEnabled = false;
                }
            }
    
            private void PassportSignInButton_Click(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
                SignInPassport();
            }
    
            private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
            {
                ErrorMessage.Text = "";
            }
    
            private async void SignInPassport()
            {
                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 MicrosoftPassportHelper.CreatePassportKeyAsync(UsernameTextBox.Text))
                    //{
                    //    Debug.WriteLine("Successfully signed in with Microsoft Passport!");
                    //}
                }
                else
                {
                    ErrorMessage.Text = "Invalid Credentials";
                }
            }
        }
    }
    
  • MicrosoftPassportHelper でメソッドを参照していたコメントされたコードに気付いたかもしれません。 MicrosoftPassportHelper.csでは、CreatePassportKeyAsync という名前の新しいメソッドを追加します。 このメソッドは、 KeyCredentialManager で Windows Hello API を使用します。 RequestCreateAsync を呼び出すと、accountId ローカル コンピューターに固有の Passport キーが作成されます。 実際のシナリオでこれを実装する場合は、switch ステートメントのコメントに注意してください。

    /// <summary>
    /// Creates a Passport key on the machine using the _account id passed.
    /// </summary>
    /// <param name="accountId">The _account id associated with the _account that we are enrolling into Passport</param>
    /// <returns>Boolean representing if creating the Passport key succeeded</returns>
    public static async Task<bool> CreatePassportKeyAsync(string accountId)
    {
        KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(accountId, KeyCredentialCreationOption.ReplaceExisting);
    
        switch (keyCreationResult.Status)
        {
            case KeyCredentialStatus.Success:
                Debug.WriteLine("Successfully made key");
    
                // In the real world authentication would take place on a server.
                // So every time a user migrates or creates a new Microsoft Passport account Passport details should be pushed to the server.
                // The details that would be pushed to the server include:
                // The public key, keyAttesation 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 Passport 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 setup Microsoft Passport
                Debug.WriteLine("Microsoft Passport is not setup!\nPlease go to Windows Settings and set up a PIN to use it.");
                break;
            default:
                break;
        }
    
        return false;
    }
    
  • CreatePassportKeyAsync メソッドを作成し、Login.xaml.cs ファイルに戻り、SignInPassport メソッド内のコードのコメントを解除しました。

    private async void SignInPassport()
    {
        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 MicrosoftPassportHelper.CreatePassportKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Microsoft Passport!");
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • アプリケーションをビルドして実行します。 ログイン ページに移動します。 「sampleUsername」と入力し、[ログイン] をクリックします。 PIN の入力を求める Windows Hello プロンプトが表示されます。 PIN を正しく入力すると、CreatePassportKeyAsync メソッドで Windows Hello キーを作成できるようになります。 出力ウィンドウを監視して、成功を示すメッセージが表示されているかどうかを確認します。

    Windows Hello ログインピンプロンプト

演習 2: ウェルカム ページとユーザー選択ページ

この演習では、前の演習から続行します。 ユーザーが正常にログインすると、サインアウトまたはアカウントを削除できるウェルカム ページに移動します。 Windows Hello によってすべてのコンピューターのキーが作成されると、ユーザー選択画面を作成できます。この画面には、そのコンピューターにサインインしているすべてのユーザーが表示されます。 その後、ユーザーはこれらのアカウントのいずれかを選択し、コンピューターにアクセスするために既に認証されているため、パスワードを再入力する必要なく、ようこそ画面に直接移動できます。

  • Views フォルダーに、"Welcome.xaml" という新しい空白ページを追加します。 次の XAML を追加して、ユーザー インターフェイスを完成させます。 これにより、タイトル、ログインしたユーザー名、および 2 つのボタンが表示されます。 いずれかのボタンがユーザー リストに戻り (後で作成します)、もう 1 つのボタンでこのユーザーの忘れ処理が行われます。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <TextBlock x:Name="Title" Text="Welcome" FontSize="40" TextAlignment="Center"/>
        <TextBlock x:Name="UserNameText" FontSize="28" TextAlignment="Center" Foreground="Black"/>
    
        <Button x:Name="BackToUserListButton" Content="Back to User List" Click="Button_Restart_Click"
                HorizontalAlignment="Center" Margin="0,20" Foreground="White" Background="DodgerBlue"/>
    
        <Button x:Name="ForgetButton" Content="Forget Me" Click="Button_Forget_User_Click"
                Foreground="White"
                Background="Gray"
                HorizontalAlignment="Center"/>
      </StackPanel>
    </Grid>
    
  • Welcome.xaml.cs分離コード ファイルに、ログインしているアカウントを保持する新しいプライベート変数を追加します。 OnNavigateTo イベントをオーバーライドするメソッドを実装する必要があります。これにより、ウェルカム ページに渡されたアカウントが格納されます。 また、XAML で定義されている 2 つのボタンのクリック イベントを実装する必要もあります。 Models フォルダーと Utils フォルダーへの参照が必要です。

    using PassportLogin.Models;
    using PassportLogin.Utils;
    using System.Diagnostics;
    
    namespace PassportLogin.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 it from Microsoft Passport
                // MicrosoftPassportHelper.RemovePassportAccountAsync(_activeAccount);
    
                // Remove it from the local accounts list and resave the updated list
                AccountHelper.RemoveAccount(_activeAccount);
    
                Debug.WriteLine("User " + _activeAccount.Username + " deleted.");
            }
        }
    }
    
  • ユーザーのクリックを忘れるイベントに行コメントが表示されることがあります。 アカウントはローカル リストから削除されていますが、現在、Windows Hello から削除する方法はありません。 Windows Hello ユーザーの削除を処理する新しいメソッドをMicrosoftPassportHelper.csに実装する必要があります。 このメソッドでは、他の Windows Hello API を使用してアカウントを開いたり削除したりします。 実際には、アカウントを削除すると、ユーザー データベースが有効なままになるように、サーバーまたはデータベースに通知する必要があります。 Models フォルダーへの参照が必要です。

    using PassportLogin.Models;
    
    /// <summary>
    /// Function to be called when user requests deleting their account.
    /// Checks the KeyCredentialManager to see if there is a Passport for the current user
    /// Then deletes the local key associated with the Passport.
    /// </summary>
    public static async void RemovePassportAccountAsync(Account account)
    {
        // Open the account with Passport
        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, RemovePassportAccountOnServer(account);
        }
    
        // Then delete the account from the machines list of Passport Accounts
        await KeyCredentialManager.DeleteAsync(account.Username);
    }
    
  • Welcome.xaml.csに戻り、RemovePassportAccountAsync を呼び出す行のコメントを解除します。

    private void Button_Forget_User_Click(object sender, RoutedEventArgs e)
    {
        // Remove it from Microsoft Passport
        MicrosoftPassportHelper.RemovePassportAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and resave the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine("User " + _activeAccount.Username + " deleted.");
    }
    
  • SignInPassport メソッド (Login.xaml.cs) で、CreatePassportKeyAsync が成功すると、ようこそ画面に移動し、アカウントを渡す必要があります。

    private async void SignInPassport()
    {
        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 MicrosoftPassportHelper.CreatePassportKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Microsoft Passport!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • アプリケーションをビルドして実行します。 "sampleUsername" でログインし、[ログイン] をクリックします。 PIN を入力すると、成功した場合はようこそ画面に移動します。 [ユーザーを忘れる] をクリックして、出力ウィンドウを監視して、ユーザーが削除されたかどうかを確認してみてください。 ユーザーが削除されると、ウェルカム ページに残ります。 アプリが移動できるユーザー選択ページを作成する必要があります。

    Windows Hello のようこそ画面

  • Views フォルダーで、"UserSelection.xaml" という名前の新しい空白ページを作成し、次の XAML を追加してユーザー インターフェイスを定義します。 このページには、ローカル アカウントリストのすべてのユーザーを表示する ListView と、ログイン ページに移動してユーザーが別のアカウントを追加できるようにするボタンが含まれます。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <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>
    
  • UserSelection.xaml.csでは、ローカル リストにアカウントがない場合にログイン ページに移動する読み込まれたメソッドを実装します。 また、ListView の SelectionChanged イベントと Button のクリック イベントを実装します。

    using System.Diagnostics;
    using PassportLogin.Models;
    using PassportLogin.Utils;
    
    namespace PassportLogin.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 LoginPage
                    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));
            }
        }
    }
    
  • アプリには、UserSelection ページに移動する場所がいくつかあります。 MainPage.xaml.csログイン ページではなく UserSelection ページに移動する必要があります。 MainPage で読み込まれたイベントが発生している間は、UserSelection ページでアカウントがあるかどうかを確認できるように、アカウントの一覧を読み込む必要があります。 これには、読み込まれたメソッドを非同期に変更し、Utils フォルダーへの参照を追加する必要があります。

    using PassportLogin.Utils;
    
    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Load the local Accounts List before navigating to the UserSelection page
        await AccountHelper.LoadAccountListAsync();
        Frame.Navigate(typeof(UserSelection));
    }
    
  • 次に、[ようこそ] ページから UserSelection ページに移動します。 どちらのクリック イベントでも、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 Microsoft Passport
        MicrosoftPassportHelper.RemovePassportAccountAsync(_activeAccount);
    
        // Remove it from the local accounts list and resave the updated list
        AccountHelper.RemoveAccount(_activeAccount);
    
        Debug.WriteLine("User " + _activeAccount.Username + " deleted.");
    
        // Navigate back to UserSelection page.
        Frame.Navigate(typeof(UserSelection));
    }
    
  • [ログイン] ページでは、[UserSelection] ページの一覧から選択したアカウントにログインするコードが必要です。 OnNavigatedTo イベントには、ナビゲーションに渡されたアカウントが格納されます。 まず、アカウントが既存のアカウントであるかどうかを識別する新しいプライベート変数を追加します。 次に、OnNavigatedTo イベントを処理します。

    namespace PassportLogin.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 Microsoft Passport 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 Microsoft Passport is setup and available on this machine
                if (await MicrosoftPassportHelper.MicrosoftPassportAvailableCheckAsync())
                {
                    if (e.Parameter != null)
                    {
                        _isExistingAccount = true;
                        // Set the account to the existing account being passed in
                        _account = (Account)e.Parameter;
                        UsernameTextBox.Text = _account.Username;
                        SignInPassport();
                    }
                }
                else
                {
                    // Microsoft Passport is not setup so inform the user
                    PassportStatus.Background = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 50, 170, 207));
                    PassportStatusText.Text = "Microsoft Passport is not setup!\n" + 
                        "Please go to Windows Settings and set up a PIN to use it.";
                    PassportSignInButton.IsEnabled = false;
                }
            }
        }
    }
    
  • 選択したアカウントにサインインするには、SignInPassport メソッドを更新する必要があります。 MicrosoftPassportHelper では、アカウントに Passport キーが既に作成されているため、Passport でアカウントを開くには別の方法が必要です。 MicrosoftPassportHelper.csに新しいメソッドを実装して、passport を使用して既存のユーザーにサインインします。 コードの各部分の詳細については、コード コメントを参照してください。

    /// <summary>
    /// Attempts to sign a message using the Passport key on the system for the accountId passed.
    /// </summary>
    /// <returns>Boolean representing if creating the Passport authentication message succeeded</returns>
    public static async Task<bool> GetPassportAuthenticationMessageAsync(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 Microsoft Passport.
    
        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 Microsoft Passport 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. Microsoft Passport has been disabled
            // 2. Microsoft Passport has been disabled and re-enabled cause the Microsoft Passport Key to change.
            // Calling CreatePassportKey and passing through the account will attempt to replace the existing Microsoft Passport Key for that account.
            // If the error really is that Microsoft Passport is disabled then the CreatePassportKey method will output that error.
            if (await CreatePassportKeyAsync(account.Username))
            {
                // If the Passport Key was again successfully created, Microsoft Passport has just been reset.
                // Now that the Passport Key has been reset for the _account retry sign in.
                return await GetPassportAuthenticationMessageAsync(account);
            }
        }
    
        // Can't use Passport right now, try again later
        return false;
    }
    
  • Login.xaml.csの SignInPassport メソッドを更新して、既存のアカウントを処理します。 これにより、MicrosoftPassportHelper.csの新しいメソッドが使用されます。 成功した場合、アカウントはサインインし、ユーザーはようこそ画面に移動します。

    private async void SignInPassport()
    {
        if (_isExistingAccount)
        {
            if (await MicrosoftPassportHelper.GetPassportAuthenticationMessageAsync(_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 MicrosoftPassportHelper.CreatePassportKeyAsync(UsernameTextBox.Text))
            {
                Debug.WriteLine("Successfully signed in with Microsoft Passport!");
                Frame.Navigate(typeof(Welcome), _account);
            }
        }
        else
        {
            ErrorMessage.Text = "Invalid Credentials";
        }
    }
    
  • アプリケーションをビルドして実行します。 "sampleUsername" でログインします。 PIN を入力すると、成功した場合はようこそ画面に移動します。 [戻る] をクリックしてユーザー一覧に戻ります。 これで、一覧にユーザーが表示されます。 この Passport をクリックすると、パスワードなどを再入力しなくても、再びサインインできます。

    Windows Hello のユーザー一覧の選択

演習 3: 新しい Windows Hello ユーザーを登録する

この演習では、Windows Hello で新しいアカウントを作成する新しいページを作成します。 これは、ログイン ページの動作と同様に機能します。 ログイン ページは、Windows Hello を使用するように移行している既存のユーザーに対して実装されます。 PassportRegister ページでは、新しいユーザーの Windows Hello 登録が作成されます。

  • views フォルダーに、"PassportRegister.xaml" という名前の新しい空白ページを作成します。 XAML で次のコードを追加して、ユーザー インターフェイスを設定します。 ここでのインターフェイスは、ログイン ページに似ています。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel Orientation="Vertical">
        <TextBlock x:Name="Title" Text="Register New Passport 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="PassportRegisterButton" Content="Register" Background="DodgerBlue" Foreground="White"
            Click="RegisterButton_Click_Async" Width="80" HorizontalAlignment="Center" Margin="0,20"/>
    
        <Border x:Name="PassportStatus" Background="#22B14C"
                   Margin="4" Height="100">
          <TextBlock x:Name="PassportStatusText" Text="Microsoft Passport is ready to use!" FontSize="20"
                 Margin="4" TextAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </StackPanel>
    </Grid>
    
  • PassportRegister.xaml.cs分離コード ファイルでは、プライベート アカウント変数と register Button のクリック イベントを実装します。 これにより、新しいローカル アカウントが追加され、Passport キーが作成されます。

    using PassportLogin.Models;
    using PassportLogin.Utils;
    
    namespace PassportLogin.Views
    {
        public sealed partial class PassportRegister : Page
        {
            private Account _account;
    
            public PassportRegister()
            {
                InitializeComponent();
            }
    
            private async void RegisterButton_Click_Async(object sender, RoutedEventArgs e)
            {
                ErrorMessage.Text = "";
    
                //In the real world you would normally validate the entered credentials and information before 
                //allowing a user to register a new account. 
                //For this sample though we will skip that step and just register an account if username is not null.
    
                if (!string.IsNullOrEmpty(UsernameTextBox.Text))
                {
                    //Register a new account
                    _account = AccountHelper.AddAccount(UsernameTextBox.Text);
                    //Register new account with Microsoft Passport
                    await MicrosoftPassportHelper.CreatePassportKeyAsync(_account.Username);
                    //Navigate to the Welcome Screen. 
                    Frame.Navigate(typeof(Welcome), _account);
                }
                else
                {
                    ErrorMessage.Text = "Please enter a username";
                }
            }
        }
    }
    
  • 登録がクリックされたときにログイン ページからこのページに移動する必要があります。

    private void RegisterButtonTextBlock_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        ErrorMessage.Text = "";
        Frame.Navigate(typeof(PassportRegister));
    }
    
  • アプリケーションをビルドして実行します。 新しいユーザーの登録を試みます。 次に、ユーザーリストに戻り、そのユーザーとログインを選択できることを確認します。

    Windows Hello で新しいユーザーを登録する

このラボでは、新しい Windows Hello API を使用して既存のユーザーを認証し、新しいユーザーのアカウントを作成するために必要な重要なスキルについて学習しました。 この新しい知識を使用すると、ユーザーがアプリケーションのパスワードを覚える必要がなくなりますが、アプリケーションがユーザー認証によって保護されたままであることを確信できます。 Windows 10 と Windows 11 では、Windows Hello の新しい認証テクノロジを使用して、生体認証ログイン オプションをサポートしています。