Epic アカウント サービス(EAS)によるプレイヤー認証

Rajen Kishna, Technical Account Manager, Epic Games |
2021年10月5日
Visual Studio 2019 でソリューション構造を設定しましたので、Epic Online Servicesの呼び出しを実装することができます。まず、Epic アカウント サービス (EAS) の認証から開始します。この記事で説明する内容は以下のとおりです。
 

Auth インターフェースと Connect インターフェース

よくある混乱の原因となるのが、Auth インターフェースConnect インターフェースの使い分けです。どちらも似たようなパターンのログイン機能を提供するためです。しかし、それぞれ特定の目的を持っています。

Auth インターフェース
  • Auth  インターフェースは、Epic アカウントへの認証を処理します。そのため、Epic アカウントサービス が設定されている必要があります。
  • Auth インターフェースを介した認証は、EOSのフレンド、プレゼンス、Eコマース機能へのアクセスを提供します。
  • Auth インターフェースは一意のEpic アカウント ID を使用します。
Connect インターフェース
  • Connect インターフェースは、Epic ゲームサービスへの認証を処理します。このインターフェースはプロバイダに依存しないため、さまざまな ID プロバイダ(Epic Games、Steam、Xbox Live など)で使用することができます。
  • Connect インターフェースは Epic アカウントに依存しません。その代わりに、組織内の特定の製品の一意の製品ユーザーID(PUID)を使用します。

簡単に言えば、Authインターフェースは Epic アカウントと関連するソーシャルグラフAPIを処理する一方、Connect インターフェースは、ゲームに代わって作成され、外部ID とリンクする必要のある、一意のユーザーIDを処理するということです。Connect インターフェースで使用されるIDはソーシャルグラフではないため、複数のIDに接続されたクロスプレイやクロスプログレッションに使用することができ、Auth インターフェースなしで使用することができます。

最後に、ユーザーが(まだ)存在していない時に、この2つのインターフェースのいずれかを使用したいシナリオもあるでしょう。たとえば、サーバー上の Web API を介して Auth インターフェイスと Connect インターフェイスの両方を使用して製品の所有権検証や、 ボイスチャットルームを作成および管理などをすることができます。 また、EOSを使用する前にプレイヤーにアカウントへのログインを強制したくない場合、Connect インターフェースの Device ID API を使用して永続的な擬似アカウントを作成し、プレイヤーがすぐにゲームをプレイできるようにするというシナリオも考えられます。この Device ID のシナリオについては、後の記事で詳しく説明します。

デベロッパーポータルでの EAS の設定

Auth インターフェースを使用してユーザーを認証し、プレゼンス情報を取得/設定し、フレンドを表示したいため、まずデベロッパーポータルでアプリケーションを設定することにより EAS をセットアップする必要があります。アプリケーションを設定するには3つのパーツがあります。Brand Settings(ブランド設定)、Permissions(許可)、Linked Clients(リンクされたクライアント)です。ブランド設定は Epic Games Store で製品を公開する際にのみ必要となるため、ここでは後者の 2 つのみを設定します。
 
  1. デベロッパーポータルhttps://dev.epicgames.com/portal/にログインします。
  2. 対象の製品に移動し、左側のメニューから Epic Account Services をクリックします。利用規約を確認し、同意する場合は承諾します。
  3. Epic Account Services の下には作成済みの製品のプレースホルダーアプリケーションが表示されます。[Configure(設定)] ボタンをクリックして設定します。
  4. ここでは、サンプルを Epic Games Store に公開しないため、[Brand Settings(ブランド設定)] タブはスキップして、右上の[Permissions(許可)] タブをクリックします。
  5. ここで、アプリケーションがユーザーに要求できる権限を設定できます。 後でその機能を実装するときに他のアクセス許可を有効にするため、今のところ、基本プロファイルのアクセス許可のみを有効にしておきます。 [Save(保存)] をクリックして確定します。
  6. [Clients(クライアント)] タブで、このアプリケーションに関連付けられているクライアントを選択できます。 [Select Clients(クライアントの選択)] ドロップダウンで以前に設定したクライアントを確認し、 [Save(保存)] をクリックして確定します。
  7. 最後に、左上の [Back(戻る)] ボタンをクリックして、デベロッパーポータルに戻ります。
Developer Portal Application Configured
アプリケーションの設定

Auth ログインの実装

前回の記事でMVVMアーキテクチャを作成しましたので、Auth のログインとログアウト機能の実装を開始します。
 
  1. ViewModelsフォルダにある MainViewModel.cs を開き、以下のメンバーを追加します。

private string _accountId;
public string AccountId
{
    get { return _accountId; }
    set { SetProperty(ref _accountId, value); }
}

private string _displayName;
public string DisplayName
{
    get { return _displayName; }
    set { SetProperty(ref _displayName, value); }
}
 
  1. MainWindow.xaml を開き、Auth インターフェイスから取得する AccountIdとDisplayName を表示する簡単なUIを作成し、ログイン ボタンとログアウト ボタンを作成します。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="1">
            <StackPanel Orientation="Horizontal">
                <Button Width="100" Height="23" Margin="2" Command="{Binding AuthLogin}" Content="Auth Login" />
                <Button x:Name="AuthLogoutButton" Width="100" Height="23" Margin="2" Command="{Binding AuthLogout}" Content="Auth Logout" />
            </StackPanel >
        </StackPanel >

        <StackPanel Grid.Column="0">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="AccountId:" Margin="2" />
                <TextBox Text="{Binding AccountId}" IsReadOnly="True" Margin="2" />
            </StackPanel >
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="DisplayName:" Margin="2" />
                <TextBox Text="{Binding DisplayName}" IsReadOnly="True" Margin="2" />
            </StackPanel >
        </StackPanel >
    </Grid>

    <TabControl Grid.Row="1" Margin="0,10,0,0">
    </TabControl>

    <Grid Grid.Row="2" Height="18">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <TextBlock Margin="2,0,0,2" Grid.Column="0" Text="{Binding StatusBarText}" />
        <ProgressBar Grid.Column="1" Height="18" Width="100" Visibility="{Binding StatusBarText, Converter={StaticResource StringToVisibilityConverter}}" IsIndeterminate="{Binding StatusBarText, Converter={StaticResource StringToBooleanConverter}}" />
    </Grid>
</Grid>
 
  • Auth UIとは別に、ステータスバーを下部に追加しました。これは、ほぼすべての呼び出しが非同期のため、サービスコールを実行している時に情報を表示し、ネットワークアクティビティを視覚的に示すプログレスバーを表示するためです。プログレスバーはコンバーターを使ってその表示を制御します。
  • 後の記事で TabControl を使用してゲームサービス機能を追加しますが、今のところ UI は次のようになります。
 VS2019 MainWindow Auth UI
MainWindow Auth UI
  1. 次に MainWindow.xaml.cs を開き、MainWindow()コンストラクタに以下の行を追加してデータ コンテキストを設定し、XAML バインディングが意図した通りに動作するようにします。

DataContext = ViewModelLocator.Main;
 
  1. 次に、Auth機能を実装するために、アプリケーション設定を編集する必要があります。ApplicationSettings.cs を開き、以下のメンバーを追加します。

public LoginCredentialType LoginCredentialType { get; private set; } = LoginCredentialType.AccountPortal;
public string Id { get; private set; } = "";
public string Token { get; private set; } = "";

public ExternalCredentialType ExternalCredentialType { get; private set; } = ExternalCredentialType.Epic;

public AuthScopeFlags ScopeFlags
{
    get
    {
        return AuthScopeFlags.BasicProfile;
    }
}
  • このサンプルでは、ブラウザを使って素早くログインできるように、AccountPortal LoginCredentialType を使用します。他のユースケースでは他のタイプを利用することもできます。
  • また、認証の対象を Epic アカウントに限定していますが、他の ExternalCredentialType もサポートされています (Nintendo、Steam、Discord など)。 
  • 最後に、 AuthScopeFlags を定義して、ユーザーからアクセスする情報を示します。これは、デベロッパーポータルのアプリケーションで設定された権限に対応している必要があります。
 
  1. 次に、Initialize() メソッドに以下を追加し、コマンドライン引数で渡されたこれらの新しいメンバーを適切に処理するようにします。

LoginCredentialType = commandLineArgs.ContainsKey("-logincredentialtype") ? (LoginCredentialType)System.Enum.Parse(typeof(LoginCredentialType), commandLineArgs.GetValueOrDefault("-logincredentialtype")) : LoginCredentialType;
Id = commandLineArgs.ContainsKey("-id") ? commandLineArgs.GetValueOrDefault("-id") : Id;
Token = commandLineArgs.ContainsKey("-token") ? commandLineArgs.GetValueOrDefault("-token") : Token;
ExternalCredentialType = commandLineArgs.ContainsKey("-externalcredentialtype") ? (ExternalCredentialType)System.Enum.Parse(typeof(ExternalCredentialType), commandLineArgs.GetValueOrDefault("-externalcredentialtype")) : ExternalCredentialType;
 
  1. EOSのインタラクション ロジックを保持するための  Services という名前のフォルダを作成し、AuthService.cs というクラスを追加します。

public static class AuthService
{
    public static void AuthLogin()
    {
        ViewModelLocator.Main.StatusBarText = "Getting auth interface...";

        var authInterface = App.Settings.PlatformInterface.GetAuthInterface();
        if (authInterface == null)
        {
            Debug.WriteLine("Failed to get auth interface");
            ViewModelLocator.Main.StatusBarText = string.Empty;
            return;
        }

        var loginOptions = new LoginOptions()
        {
            Credentials = new Credentials()
            {
                Type = App.Settings.LoginCredentialType,
                Id = App.Settings.Id,
                Token = App.Settings.Token,
                ExternalType = App.Settings.ExternalCredentialType
            },
            ScopeFlags = App.Settings.ScopeFlags
        };

        ViewModelLocator.Main.StatusBarText = "Requesting user login...";

        authInterface.Login(loginOptions, null, (LoginCallbackInfo loginCallbackInfo) =>
        {
            Debug.WriteLine($"Auth login {loginCallbackInfo.ResultCode}");

            if (loginCallbackInfo.ResultCode == Result.Success)
            {
                ViewModelLocator.Main.StatusBarText = "Auth login successful.";

                ViewModelLocator.Main.AccountId = loginCallbackInfo.LocalUserId.ToString();

                var userInfoInterface = App.Settings.PlatformInterface.GetUserInfoInterface();
                if (userInfoInterface ==  null)
                {
                    Debug.WriteLine("Failed to get user info interface");
                    return;
                }

                var queryUserInfoOptions = new QueryUserInfoOptions()
                {
                    LocalUserId = loginCallbackInfo.LocalUserId,
                    TargetUserId = loginCallbackInfo.LocalUserId
                };

                ViewModelLocator.Main.StatusBarText = "Getting user info...";

                userInfoInterface.QueryUserInfo(queryUserInfoOptions, null, (QueryUserInfoCallbackInfo queryUserInfoCallbackInfo) =>
                {
                    Debug.WriteLine($"QueryUserInfo {queryUserInfoCallbackInfo.ResultCode}");

                    if (queryUserInfoCallbackInfo.ResultCode == Result.Success)
                    {
                        ViewModelLocator.Main.StatusBarText = "User info retrieved.";

                        var copyUserInfoOptions = new CopyUserInfoOptions()
                        {
                            LocalUserId = queryUserInfoCallbackInfo.LocalUserId,
                            TargetUserId = queryUserInfoCallbackInfo.TargetUserId
                        };

                        var result = userInfoInterface.CopyUserInfo(copyUserInfoOptions, out var userInfoData);
                        Debug.WriteLine($"CopyUserInfo: {result}");

                        if (userInfoData != null)
                        {
                            ViewModelLocator.Main.DisplayName = userInfoData.DisplayName;
                        }

                        ViewModelLocator.Main.StatusBarText = string.Empty;
                        ViewModelLocator.RaiseAuthCanExecuteChanged();
                    }
                });
            }
            else if (Common.IsOperationComplete(loginCallbackInfo.ResultCode))
            {
                Debug.WriteLine("Login failed: " + loginCallbackInfo.ResultCode);
            }
        });
    }
}

これが Auth ログイン実装の要点なので、詳しく見ていきましょう。
 
  • 関数全体で、ViewModelLocator.Main.StatusBarText を使用してUIにステータスバーテキストを設定します。これにより、テキストが空ではない場合にプログレスバーが自動的に表示されます。
  • PlatformInterface.GetAuthInterface() を使用して、関数全体で使用できる Auth インターフェイスのインスタンスを取得します。
  • EOSのすべての Interface 関数は、"options "クラスをインスタンス化する必要があり、これはサービスコールに渡され、多くの場合、コールバックメソッドのイベント ハンドラを伴います。例えば、LoginOptions() に ApplicationSettings.cs からの認証情報とスコープをインスタンス化して、authInterface.Login() に渡します。
  • authInterface.Login()に lamda callback メソッドを渡して、ログインレスポンスを処理します。このコールバックメソッドは、タイマーが更新されたときに MainWindow.xaml.cs のApp.Settings.PlatformInterface?Tick() を呼び出すことで起動します。
  • コールバックでは、ResultCode をチェックして、それが Result.Success と等しければ、ログインコールが成功したことになります。成功しなかった場合は、失敗メッセージとそれに対応する ResultCode を Debug 出力に書き込みます。
  • ログインが成功した場合、loginCallbackInfo.LocalUserId を MainViewModel インスタンスに保存し、アプリケーション内の他のサービスコールで使用できるようにします。
  • 最後に、PlatformInterface.GetUserInfoInterface() を使って UserInfo Interface のインスタンスを取得し、userInfoInterface.QueryUserInfo()を使って追加のユーザ情報(DisplayNameなど)を取得します。

ViewModelLocator.RaiseAuthCanExecuteChanged() が解決できないことがわかりますが、これについては次に取り組みます。
 
  1. プロジェクトのルートに Commands という名前のフォルダを作り、AuthLoginCommand.cs というクラスを追加します。これは、MainWindow.xaml のログインボタンから起動されるコマンドです。

public class AuthLoginCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    }

    public override void Execute(object parameter)
    {
        AuthService.AuthLogin();
    }
}
 
  1. MainViewModel.cs を開き、次のメンバーを追加します。

public AuthLoginCommand AuthLogin { get; set; }
 
  1. さらに、次の行を追加して、MainViewModel() コンストラクターでコマンドをインスタンス化します。

AuthLogin = new AuthLoginCommand();
 
  1. ViewModelLocator.cs を開き、RaiseAuthCanExecuteChanged() 関数を実装して、ユーザーがログインしていない場合にのみログインボタンが有効になるようにします。

public static void RaiseAuthCanExecuteChanged()
{
    Main.AuthLogin.RaiseCanExecuteChanged();
}

これで、アプリケーションを実行し、ログインボタンをクリックすると、Epic アカウントを使用して認証するためのブラウザウィンドウが表示されます。このアプリケーションはまだ開発段階にあるため、デベロッパーポータルの組織に属する Epic アカウントを使用してのみログインすることができます。ログインすると、このアプリケーションはブランド審査を受けていないため、Unverified Application (未検証のアプリケーション) という通知が表示されます。続行すると、ユーザー同意ダイアログが表示され、製品がアクセスを要求している範囲(このサンプルケースでは基本的なプロフィール情報)が表示されます。[Allow(許可)] をクリックすると、ブラウザが終了し、コールバックメソッドが起動して、DisplayNameを取得し、AccountIdとともにアプリのUIに表示します。
 
Auth Unverified Application Auth User Consent
未検証のアプリケーション警告とユーザー同意ダイアログ 

Auth ログアウトの実装

これで構造ができましたので、ログアウトの実装はさらに簡単になりました。
 
  1. AuthService.csを開き、Logout()メソッドを追加します。

public static void AuthLogout()
{
    var logoutOptions = new LogoutOptions()
    {
        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
    };

    App.Settings.PlatformInterface.GetAuthInterface().Logout(logoutOptions, null, (LogoutCallbackInfo logoutCallbackInfo) =>
    {
        Debug.WriteLine($"Logout {logoutCallbackInfo.ResultCode}");

        if (logoutCallbackInfo.ResultCode == Result.Success)
        {
            ViewModelLocator.Main.StatusBarText = "Logout successful.";

            var deletePersistentAuthOptions = new DeletePersistentAuthOptions();
            App.Settings.PlatformInterface.GetAuthInterface().DeletePersistentAuth(deletePersistentAuthOptions, null, (DeletePersistentAuthCallbackInfo deletePersistentAuthCallbackInfo) =>
            {
                Debug.WriteLine($"DeletePersistentAuth {logoutCallbackInfo.ResultCode}");

                if (logoutCallbackInfo.ResultCode == Result.Success)
                {
                    ViewModelLocator.Main.StatusBarText = "Persistent auth deleted.";

                    ViewModelLocator.Main.AccountId = string.Empty;
                    ViewModelLocator.Main.DisplayName = string.Empty;

                    ViewModelLocator.Main.StatusBarText = string.Empty;
                    ViewModelLocator.RaiseAuthCanExecuteChanged();
                }
            });
        }
        else if (Common.IsOperationComplete(logoutCallbackInfo.ResultCode))
        {
            Debug.WriteLine("Logout failed: " + logoutCallbackInfo.ResultCode);
        }
    });
}
 
  1. Commands フォルダに AuthLogoutCommand.cs という新しいクラスを追加します。

public class AuthLogoutCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    }

    public override void Execute(object parameter)
    {
        AuthService.AuthLogout();
    }
}
 
  1. MainViewModel.cs に新しいコマンド用のメンバーとインスタンスを追加します。

public AuthLoginCommand AuthLogin { get; set; }
public AuthLogoutCommand AuthLogout { get; set; }

public MainViewModel()
{
    AuthLogin = new AuthLoginCommand();
    AuthLogout = new AuthLogoutCommand();
}
 
  1. 最後に、ViewModelLocator.cs の RaiseAuthCanExecuteChanged() メソッドに次の行を追加します。

Main.AuthLogout.RaiseCanExecuteChanged();

F5キーを押してアプリケーションを実行すると、ログインフローが完了するまで Auth ログアウト ボタンが無効になり、その後は正常にログアウトできることがわかります。

コードの取得

この記事のコードは以下の通りです。Visual Studio 2019 で EOS 用に C# ソリューションを設定する方法の記事にある C# ソリューションの設定セクション 5 と10の手順に従って SDK をソリューションに追加し、ApplicationSettings.cs を編集して SDK の認証情報を含めるようにしてください。
 
次回は、ユーザープレゼンス情報の取得と設定について説明します。 このシリーズのすべての記事は、シリーズ記事一覧からいつでもご覧いただけます。

    あなたが成功するとき、私たちは成功します

    Epic では、オープンで調和のとれたゲーム コミュニティを信じています。オンライン サービスをすべての人に無料で提供することにより、より多くのデベロッパーがそれぞれのプレイヤー コミュニティにサービスを提供できるようにすることが目標です。