ユニバーサル Windows プラットフォームを使用して PlayFab 認証を設定する
このチュートリアルでは、ユニバーサル Windows プラットフォーム (UWP) を使用した PlayFab 認証の手順を説明します。
Important
この手順は、Windows Hello と PlayFab を介してすべてのエンティティを取得し、認証をコミットする方法の非常に基本的な説明です。 Windows Hello と PlayFab のより高度な例は、当社の [UWPExample project](https://github.com/PlayFab/UWPExample)
を検討してください。
要件
- MSDN 「設定」ガイド に従って、UWP 開発に向けて Windows と Visual Studio を準備する。
- 登録済みの PlayFab タイトルがあること。
- 「ログインの基本とベスト プラクティス」を理解していること。
注意
Windows 10 オペレーティング システムを使用していて、検証済みの Microsoft アカウントでログインし、PIN などのインターフェイスへのアクセスを構成していることが非常に重要です。 これらの要件が満たされていない場合、アプリケーションは役立つ理由の説明なしで失敗します。
Visual Studio プロジェクトを準備する
Visual Studio を開始し、新しいプロジェクトを作成します。
- [テンプレート] で [Windows ユニバーサル] を選択します。
- 次に、[空白のアプリ (ユニバーサル Windows)] のタイプを選択します。
- 名前を GettingStartedPlayfabUWP とします。
- [OK] ボタンを選択して送信します。
- プロジェクトに一致する [ターゲット バージョン] と [最小バージョン] を選択します。
- [OK] ボタンを選択します。
プロジェクトが作成されたら、NuGet パッケージ マネージャーを使用して PlayFab SDK を追加します。
- まず、[ツール] タブを選択します。
- ドロップダウン メニューで、[NuGet パッケージ マネージャー] を選択します。
- 次に、[ソリューションに向けた NuGet パッケージ管理] を選択します。
[NuGet マネージャー] ウィンドウで以下を実行します。
- [参照] を選択し、「PlayFabAllSDK」パッケージを検索します。
- ターゲット プロジェクトを選択します。
- 次に、[インストール] ボタンを選択します。
終了すると、基本的なプロジェクトの設定は完了です。 次のセクションでは、プロジェクトの作成時に自動的に生成される 2 つのクラスを変更します。
- アプリ
- MainPage
実装
App.xaml.cs
このクラスは、単に適切なタイトル ID を設定して PlayFab SDK を設定します。 独自のタイトル ID に置き換えることを忘れないでください。
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace GettingStartedPlayfabUWP
{
// This class is generated upon project creation.
// While template on it's own contains a lot of xml comments, we are only interested in lines 17 and 27
sealed partial class App : Application
{
// Replace PLAYFAB_TITLE_ID with your own
public const string PlayfabTitleId = "PLAYFAB_TITLE_ID";
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
// This is the only line of functional code we need to add to this class.
PlayFab.PlayFabSettings.TitleId = PlayfabTitleId;
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}
MainPage.xaml
このファイルには、メイン ページのレイアウトが含まれています。 これは、ボタンが 2 つで、テキスト入力が垂直方向のグリッドで組み合わされた非常に単純なレイアウトです。
ボタンは特定のメソッドにバウンドされており、テキストボックスは UsernameInput
の名前でアクセスできます。
<Page
x:Class="GettingStartedPlayfabUWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GettingStartedPlayfabUWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="0,10,10,0">
<TextBox x:Name="UsernameInput" TextWrapping="Wrap" Text="" PlaceholderText="Username..."/>
<Button x:Name="RegisterButton" Content="Register With Hello" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="RegisterRequest"/>
<Button x:Name="LoginButton" Content="Sign In With Hello" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Click="LogInRequest"/>
</StackPanel>
</Grid>
</Page>
MainPage.xaml.cs
これはメイン ページの機能クラスであり、この例の中心です。 コード コメントを参照して、PlayFab+Hello の登録とログインを説明するために設計された異なるメソッドを確認してください。
コードの学習を開始する最も簡単な方法は、対応するボタンによってトリガーされるメソッドを確認することです。
- RegisterRequest
- LogInRequest。
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking.Connectivity;
using Windows.Security.Credentials;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using PlayFab;
using PlayFab.ClientModels;
namespace GettingStartedPlayfabUWP
{
public sealed partial class MainPage : Page
{
// Shortcut to get current value of UsernameInput
public string Username => UsernameInput.Text;
public MainPage()
{
this.InitializeComponent();
}
/// <summary>
/// This method is invoked when you select the Register button
/// This method illustrates the flow for Registration process.
/// We operate on 2 entities:
/// - User Credentials of type KeyCredential
/// - Public Key of type String
/// We first check if user with this id already has Credentials. If so, we redirect to login procedure.
/// Then we create new User Credentials. Check CreateKeyCredential for implementation details
/// Then we get Base64 encoded Public Key using the new User Credentials. Check GetPublicKeyBase64 for implementation details
/// Then we execute RegisterWithHello api call call. Check CallPlayFabRegisterWithHello for implementation details
/// </summary>
private async void RegisterRequest(object sender, RoutedEventArgs e)
{
// Check if the user already exists and if so log them in.
KeyCredentialRetrievalResult retrieveResult = await KeyCredentialManager.OpenAsync(Username);
if (retrieveResult.Status == KeyCredentialStatus.Success)
{
// Redirect to login procedure
LogInRequest(sender, e);
return;
}
// Create a new KeyCredential for the user on the device.
var credential = await CreateKeyCredential(Username);
if (credential == null) return;
var publicKey = await GetPublicKeyBase64(credential);
if (string.IsNullOrEmpty(publicKey)) return;
// Include the name of the current device for the benefit of the user.
// The server could support a Web interface that shows the user all the devices they
// have signed in from and revoke access from devices they have lost.
var registerResponse = await CallPlayFabRegisterWithHello(publicKey, Username);
await ShowMessage("Registered and signed in with Session Ticket " + registerResponse.Result.SessionTicket);
}
//
/// <summary>
/// This method is invoked when you select the Log In button
/// This method shows entities flow during the sign in process.
/// We have 4 different entities:
/// - User Credentials of type KeyCredential
/// - Public Key Hint of type String
/// - Challenge of type String
/// - SignedChallenge of type String
///
/// We first acquire the User Credentials. We do it based on Username. Check GetUserCredentials method for implementation details
/// Next, we get Public Key Hint based on those credentials. Check GetPublicKeyHint for implementation details.
/// Next we request a Challenge from PlayFab. Check GetPlayFabHelloChallenge for implementation details
/// Next we sign the Challenge using User Credentials, so we obtain Signed Challenge. Check GetPlayFabHelloChallenge for implementation details
/// Finally we use Signed Challenge and Public Key Hint to log into PlayFab. Check CallPlayFabLoginWithHello for implementation details
/// </summary>
private async void LogInRequest(object sender, RoutedEventArgs e)
{
// Get credentials based on current Username.
var credentials = await GetUserCredentials(Username);
if (credentials == null) return;
// Credentials will give us Public Key. We use it to construct Public Key Hint, which is first important entity for PlayFab+UWP authentication.
var publicKeyHint = GetPublicKeyHintBase64(credentials);
if (string.IsNullOrEmpty(publicKeyHint)) return;
// Get PlayFab Challenge to sign for Windows Hello.
var challenge = await GetPlayFabHelloChallenge(publicKeyHint);
if (string.IsNullOrEmpty(challenge)) return;
// Request user to sign the challenge.
var signedChallenge = await RequestUserSignChallenge(credentials, challenge);
if (string.IsNullOrEmpty(signedChallenge)) return;
// Send the signature back to the server to confirm our identity.
// The publicKeyHint tells the server which public key to use to verify the signature.
var result = await CallPlayFabLoginWithHello(publicKeyHint, signedChallenge);
if (result == null) return;
// Report the result.
await ShowMessage("Signed in with Session Ticket " + result.Result.SessionTicket);
}
public async Task<string> GetPublicKeyBase64(KeyCredential userCredential)
{
IBuffer publicKey = userCredential.RetrievePublicKey();
if (publicKey == null)
{
await ShowMessage("Failed to get public key for credential");
return null;
}
return CryptographicBuffer.EncodeToBase64String(publicKey);
}
public string GetPublicKeyHintBase64(KeyCredential userCredential)
{
HashAlgorithmProvider hashProvider = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var publicKey = userCredential.RetrievePublicKey();
IBuffer publicKeyHash = hashProvider.HashData(publicKey);
return CryptographicBuffer.EncodeToBase64String(publicKeyHash);
}
public async Task<KeyCredential> GetUserCredentials(string userId)
{
// Open credential based on our Username and make sure it is successful
KeyCredentialRetrievalResult retrieveResult = await KeyCredentialManager.OpenAsync(userId);
if (retrieveResult.Status != KeyCredentialStatus.Success)
{
await ShowMessage("Error: Unable to open credentials! " + retrieveResult.Status);
return null;
}
return retrieveResult.Credential;
}
public async Task<string> GetPlayFabHelloChallenge(string publicKeyHint)
{
// Request challenge from PlayFab and make sure response has no errors
var challengeResponse = await PlayFab.PlayFabClientAPI.GetWindowsHelloChallengeAsync(new GetWindowsHelloChallengeRequest
{
PublicKeyHint = publicKeyHint,
TitleId = PlayFab.PlayFabSettings.TitleId
});
if (challengeResponse.Error != null)
{
await ShowMessage($"Error during getting challenge: {challengeResponse.Error.Error}");
return null;
}
return challengeResponse.Result.Challenge;
}
public async Task<string> RequestUserSignChallenge(KeyCredential credentials, string challenge)
{
IBuffer challengeBuffer = CryptographicBuffer.DecodeFromBase64String(challenge);
KeyCredentialOperationResult opResult = await credentials.RequestSignAsync(challengeBuffer);
if (opResult.Status != KeyCredentialStatus.Success)
{
await ShowMessage("Failed sign the challenge string: " + opResult.Status);
return null;
}
return CryptographicBuffer.EncodeToBase64String(opResult.Result);
}
public async Task<PlayFabResult<LoginResult>> CallPlayFabLoginWithHello(string publicKeyHint, string signedChallenge)
{
var loginResponse = await PlayFab.PlayFabClientAPI.LoginWithWindowsHelloAsync(new LoginWithWindowsHelloRequest
{
ChallengeSignature = signedChallenge,
PublicKeyHint = publicKeyHint
});
if (loginResponse.Error != null)
{
await ShowMessage($"Failed to log in: {loginResponse.Error.Error}");
return null;
}
return loginResponse;
}
public IAsyncOperation<IUICommand> ShowMessage(string messageString)
{
MessageDialog message = new MessageDialog($"{messageString}");
return message.ShowAsync();
}
public async Task<PlayFabResult<LoginResult>> CallPlayFabRegisterWithHello(string publicKey, string username)
{
var hostNames = NetworkInformation.GetHostNames();
var localName = hostNames.FirstOrDefault(name => name.DisplayName.Contains(".local"));
string computerName = localName.DisplayName.Replace(".local", "");
var registerResult = await PlayFab.PlayFabClientAPI.RegisterWithWindowsHelloAsync(new RegisterWithWindowsHelloRequest
{
DeviceName = computerName,
PublicKey = publicKey,
UserName = username
});
if (registerResult.Error != null)
{
await ShowMessage(registerResult.Error.GenerateErrorReport());
return null;
}
return registerResult;
}
public async Task<KeyCredential> CreateKeyCredential(string username)
{
KeyCredentialRetrievalResult keyCreationResult = await KeyCredentialManager.RequestCreateAsync(username, KeyCredentialCreationOption.ReplaceExisting);
if (keyCreationResult.Status != KeyCredentialStatus.Success)
{
// User has authenticated with Windows Hello and the key credential is created.
await ShowMessage("Failed to create key credential: " + keyCreationResult.Status);
return null;
}
return keyCreationResult.Credential;
}
}
}
テスト
以下の方法でアプリケーションを実行します。
- ユーザー名とパスワードを入力します。
- [Hello で登録] を選択します。
Windows で認証のために提示される指示に従います。
- ID が確認されると、アカウントが登録およびサインイン されたことを示す確認メッセージが表示されます。
- セッション チケットを使用して。
- [Hello でサインイン] ボタンを選択します。
Windows で認証のために提示される指示に従います。
- ID が確認されると、アカウントがサインイン されたことを示す確認メッセージが表示されます。
- セッション チケットを使用して。
これで、PlayFab と UWP アプリケーションが正常に統合されました。