> For the complete documentation index, see [llms.txt](/llms.txt)

# GS2-Account

アカウント管理機能




Game Server Services が提供するアカウントシステムは「匿名アカウント」という種類のアカウントシステムです。
日本ではメジャーな仕組みですが、多くの地域では奇妙に感じるアカウントシステムかもしれません。

しかし、このアカウントシステムはゲームにおいて非常に理にかなったアカウントシステムです。

## 匿名アカウントとは何ですか？

通常イメージするアカウント管理は、ログインIDがありパスワードがあるものでしょう。
匿名アカウントもその点は変わりません。

しかし、ログインIDとパスワードの両方がシステムによってランダムに決定されるというのが最大の特徴です。

システムによって発行されたログインID・パスワードをデバイスのローカルストレージに保存し、2回目以降はその情報を利用してログインすることでゲームを再開できます。

```mermaid
graph TD
  Startup --> LoadSave{"デバイスストレージから<br/>匿名アカウントを読み込み"}
  LoadSave -- Not Exists --> CreateAccount["匿名アカウントを作成"]
  CreateAccount --> SaveAccount["匿名アカウントを保存"]
  SaveAccount --> Login["ログイン"]
  LoadSave -- Exists --> Login
  Login --> InGame
```

### 匿名アカウントのメリット

- 簡単な登録：ユーザーは煩雑な登録プロセスを経ずにゲームをすぐに開始することができます。特に、無料で遊べるF2Pスタイルのゲームでは、多くのユーザーがゲームを試してもらうためには登録の手間を減らすことが重要です。
- プレイヤー情報の保護：ログインIDやパスワードを自分で設定する必要がないため、個人情報やプライバシーが保護されます。また、パスワードの再設定や変更なども必要ありません。
- 引き継ぎのしやすさ：引き継ぎ情報に各種プラットフォームのID基盤やSNSのアカウントなどを利用することができ、異なるデバイスやアプリ間での引き継ぎがスムーズに行えます。
- 匿名でのプレイ体験：プレイヤーは匿名でゲームをプレイすることができるため、リアルネームやニックネームを使用しなくてもよく、より自由なプレイ体験を楽しむことができます。

## 引き継ぎ

デバイスのローカルストレージほど信頼性の低い場所は他にはありません。
デバイスを落としてしまうかもしれませんし、デバイスを壊してしまうかもしれません。
そのとき、ゲームデータを全て失ってしまうとしたら、それは絶望的な状況です。

ゲームプレイヤーは、匿名アカウントでゲームを体験し、本当に気に入ったら「引き継ぎ情報」を登録できます。
引き継ぎ情報には各種プラットフォーマーのID基盤の情報を利用してもいいですし、SNSのアカウントを設定してもいいでしょう。ゲームパブリッシャーのID基盤も良さそうです。

引き継ぎ情報はスロット番号0〜1024の範囲で管理され、最大1,025種類まで登録できます。
用途に応じて各スロットに異なる認証手段を紐付けることで、幅広い引き継ぎシナリオを実現できます。

引き継ぎ情報には各種ID基盤で認証した結果得られるユーザーIDなどを記録します。
そして、全く新しいデバイスで引き継ぎを実行します。各種ID基盤にログインし、得られたユーザーIDで引き継ぎを実行すると
過去に引き継ぎ情報を設定した匿名アカウントでログインするための情報を取得することができます。

この情報を再びデバイスのローカルストレージに保存して、今後のログインに利用します。

```mermaid
graph TD
  InGame -- ゲームを気に入った --> AddTakeOverSetting["引き継ぎ情報を登録"]
  AddTakeOverSetting --> BrokenDevice["スマホが壊れた"]
  NewDevice["新しいスマホ"] --> DoTakeOver["引き継ぎ情報を入力して<br/>引き継ぎを実行"]
  DoTakeOver -- 匿名アカウントを復元 --> SaveAccount["匿名アカウントを保存"]
  SaveAccount --> Login["ログイン"]
```

## OpenID Connect 連携

引き継ぎ情報の登録、引き継ぎ処理の実行に OpenID Connect に準拠した認証システムを使用するためのサポートが用意されています。

### 認証サービスの登録

OpenID Connect 連携機能を使用するにはマスターデータの設定が必要です。各種引き継ぎスロットごとに連携先を指定でき、最大1,025種類の認証手段に対応できます。設定項目は以下の通りです。

- 認証サービスの OpenID Connect Discovery エンドポイント URL
- 認証サービスのクライアントID
- 認証サービスのクライアントシークレット
- 認証完了後に遷移するURL
- 追加で要求するスコープ値
- 認証結果で取得する追加の返却値

Sign in with Apple は クライアントシークレット を動的に計算する必要がありますが、この計算機能も備えています。
認証サービスの OpenID Connect Discovery エンドポイント URL に Sign in with Apple のURLを指定した場合は、クライアントシークレットの代わりに

- Apple Developer のチームID
- Apple から発行された秘密鍵のID
- Apple から発行された秘密鍵のペイロード(PEM)

を登録でき、クライアントシークレットは GS2 が必要に応じて計算します。

### 認証処理

認証処理に関するサポートも提供しています。

認証サービスの認証ページのURL取得APIや、認証サービスでの認証後にコールバックを受け取るためのエンドポイントを提供しています。

認証サービスのコールバック URL には以下のフォーマットに従った値を設定してください。

```
https://account.{region}.gen2.gs2io.com/{ownerId}/{namespaceName}/type/{type}/callback

https://account.ap-northeast-1.gen2.gs2io.com/aAbBcCdD-project/namespace-0001/type/0/callback
```

認証のコールバックを受け取ると以下のURLに遷移します。

```
https://account.{region}.gen2.gs2io.com/{ownerId}/{namespaceName}/type/{type}/done?id_token={idToken}
```

URLのクエリストリングに認証して得られたIDトークンが渡ってきます。

#### Firebase Authentication のような GS2 以外の認証機能の利用

IDトークンさえ手に入ればその手段は上記手順である必要はありません。

#### 認証サービス以外からの登録を拒否

マスターデータで OpenID Connect 連携が設定されたスロットは任意のユーザー識別子とパスワードを使用した引き継ぎ情報の登録および、引き継ぎの実行は行えなくなります。

## 実装例

### 匿名アカウントの作成

GS2-Account はネームスペースという階層をもち、1つのプロジェクト内に複数のアカウントプールを持つことができます。
ネームスペースの用途は特に定めていません、配信地域ごとにネームスペースを分けるなど必要に応じて活用することができます。



**Unity**
```csharp

    var result = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).CreateAsync();

    var item = await result.ModelAsync();
    var userId = item.UserId;
    var password = item.Password;
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";

    const auto Future = Gs2->Account->Namespace(
        NamespaceName
    )->Create();

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();

    const auto Future2 = Result->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result2 = Future2->GetTask().Result();

    const auto UserId = Result2->UserId;
    const auto Password = Result2->Password;
```


### 匿名アカウントを使用したログイン

GS2-Account の認証処理は、一定期間でやりなおす必要があります。
そのような処理を自動的に行ってくれるユーティリティクラスである Gs2AccountAuthenticator を使用したログイン例を示します。

GS2 のAPIクライアントに対して、Gs2AccountAuthenticatorと作成した匿名アカウントのユーザーID、パスワードを指定することでログイン中のセッション情報を表す GameSession インスタンスを取得できます。
以降、ログイン中のプレイヤーの情報にアクセスするにはこの GameSession インスタンスを使用することになります。



**Unity**
```csharp

    var gameSession = await gs2.LoginAsync(
        new Gs2AccountAuthenticator(
            accountSetting: new AccountSetting {
                accountNamespaceName = this.accountNamespaceName,
            }
        ),
        account.UserId,
        account.Password
    );
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";
    const auto KeyId = "grn:gs2:{region}:{yourOwnerId}:key:namespace-0001:key:key-0001";

    const auto Future = Profile->Login(
        MakeShareable<Gs2::UE5::Util::IAuthenticator>(
            new Gs2::UE5::Util::FGs2AccountAuthenticator(
                NamespaceName,
                KeyId
            )
        ),
        UserId,
        Password
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();
```


#### 認証時の特殊例外

Authentication に相当する処理では、以下の特殊例外が発生する場合があります。

| 例外型 | 基底型 | 説明 |
| --- | --- | --- |
| `PasswordIncorrectException` | `UnauthorizedException` | パスワードが一致しない |
| `BannedInfinityException` | `UnauthorizedException` | アカウントが利用停止されている |

ゲーム側ではこれらの例外を適切にハンドリングし、ユーザーに対してエラーメッセージを表示することを推奨します。
`Gs2AccountAuthenticator` / `FGs2AccountAuthenticator` 経由のログインで上記例外がどのような形で露出するかは SDK ラッパーの実装に依存するため、詳細は [API リファレンス]() を参照してください。

### 引き継ぎ情報の登録

スロット番号 に異なる値を指定することで、1つのアカウントに対して複数の 引き継ぎ設定 を保持できます。
スロット番号は0〜1024の範囲で指定でき、最大1025種類の引き継ぎ方法を登録できます。

たとえば、 スロット番号:0 にメールアドレス・パスワード を、 スロット番号:1 にソーシャルメディアのID情報を格納するようにし、
ゲームプレイヤーは好みの引き継ぎ手段を選択できるようにする といった運用が可能です。

#### 引き継ぎ設定のパスワード取り扱い

引き継ぎ情報で登録する password は、サーバー側でハッシュ化して保存されます。
一度設定されたパスワードは API で再取得できず、忘失した場合は新しい引き継ぎ設定を発行し直す必要があります。
この仕組みにより、プレイヤーの認証情報を安全に保持しつつ、不正利用を防止します。



**Unity**
```csharp

    var result = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).TakeOver(
        type: 0
    ).AddTakeOverSettingAsync(
        userIdentifier: "user-0001@gs2.io",
        password: "password-0001"
    );
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";
    const auto Type = 0;
    const auto UserIdentifier = "user-0001@gs2.io";
    const auto Password = "password-0001";

    const auto Future = Gs2->Account->Namespace(
        NamespaceName
    )->Me(
        AccessToken
    )->TakeOver(
        Type
    )->AddTakeOverSetting(
        UserIdentifier,
        Password
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();
```


### 登録済みの引き継ぎ情報一覧取得



**Unity**
```csharp

    var items = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).TakeOversAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";

    const auto It = Gs2->Account->Namespace(
        NamespaceName
    )->Me(
        AccessToken
    )->TakeOvers();
    TArray<Gs2::UE5::Account::Model::FEzTakeOverPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```


### 引き継ぎの実行



**Unity**
```csharp

    string userId;
    string password;
    try {
        var result = await gs2.Account.Namespace(
            namespaceName: "namespace-0001"
        ).DoTakeOverAsync(
            type: 0,
            userIdentifier: "user-0001@gs2.io",
            password: "password-0001"
        );

        var item = await result.ModelAsync();
        userId = item.UserId;
        password = item.Password;
    } catch(Gs2.Gs2Account.Exception.PasswordIncorrectException e) {
        // Incorrect password specified.
    }
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";
    const auto Type = 0;
    const auto UserIdentifier = "user-0001@gs2.io";
    const auto Password = "password-0001";

    const auto Future = Gs2->Account->Namespace(
        NamespaceName
    )->DoTakeOver(
        Type,
        UserIdentifier,
        Password
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        auto e = Future->GetTask().Error();
        if (e->IsChildOf(Gs2::Account::Error::FPasswordIncorrectError::Class))
        {
            // Incorrect password specified.
        }
        return false;
    }
    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
```


### OpenID Connect の認証処理

以下のサンプルのアプリ内ブラウザには [unity-webview](https://github.com/gree/unity-webview) を使用しています。

```cs
    public static async UniTask<string> OpenAuthentication(
        WebViewObject webView,
        Gs2Domain gs2,
        string namespaceName,
        IGameSession gameSession,
        int type
    ) {
        string idToken = null;
        webView.Init(
            separated: true,
            ld: url =>
            {
                if (new Uri(url).LocalPath.EndsWith("/done")) {
                    var codeField = new Uri(url).Query.Replace("?", "").Split("&").Select(v => new KeyValuePair<string,string>(v[..v.IndexOf("=", StringComparison.Ordinal)], v[(v.IndexOf("=", StringComparison.Ordinal)+1)..])).FirstOrDefault(v => v.Key == "id_token");
                    idToken = Uri.UnescapeDataString(codeField.Value);
                    webView.SetVisibility(false);
                }
            }
        );
        webView.LoadURL(
            (await gs2.Account.Namespace(
                namespaceName
            ).Me(
                gameSession
            ).GetAuthorizationUrlAsync(
                type
            )).AuthorizationUrl
        );
        webView.SetInteractionEnabled(true);
        webView.SetVisibility(true);

        await UniTask.WaitWhile(() => idToken == null);

        return idToken;
    }
```

### OpenID Connect を使用した引き継ぎ情報の登録



**Unity**
```csharp

    var result = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).TakeOver(
        type: 0
    ).AddTakeOverSettingOpenIdConnectAsync(
        idToken: "id-token"
    );
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";
    const auto Type = 0;

    const auto Future = Gs2->Account->Namespace(
        NamespaceName
    )->Me(
        AccessToken
    )->TakeOver(
        Type
    )->AddTakeOverSettingOpenIdConnect(
        "id-token"
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();
```


### OpenID Connect を使用した引き継ぎの実行




**Unity**
```csharp

    var result = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).DoTakeOverOpenIdConnectAsync(
        type: 0,
        idToken: "id-token"
    );

    var item = await result.ModelAsync();
    var userId = item.UserId;
    var password = item.Password;
```
**Unreal Engine**
```cpp

    const auto NamespaceName = "namespace-0001";
    const auto Type = 0;

    const auto Future = Gs2->Account->Namespace(
        NamespaceName
    )->DoTakeOverOpenIdConnect(
        Type,
        "id-token"
    );

    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }
    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
```


## その他の機能

### プラットフォームID管理

GS2-Account では、X や Instagram などの各種プラットフォームで利用されるユーザーIDをアカウントに紐付けて保存できます。他のプレイヤーはこれらのIDで検索し、SNS上の友人をゲーム内に招待するといった連携が可能です。プラットフォームIDも引き継ぎ情報と同様に0〜1024のスロット番号で管理され、1アカウントに複数登録できます。

#### プラットフォームIDの登録



**Unity**
```csharp

    var domain = gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PlatformId(
        type: 0,
        userIdentifier: "123456"
    );
    var result = await domain.AddPlatformIdSettingAsync(
    );
    var item = await result.ModelAsync();
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Account->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->PlatformId(
        0, // type
        "123456" // userIdentifier
    );
    const auto Future = Domain->AddPlatformIdSetting(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
```


#### プラットフォームIDによるユーザー検索



**Unity**
```csharp

    var domain = gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PlatformId(
        type: 0,
        userIdentifier: "123456"
    );
    var result = await domain.FindPlatformUserAsync(
    );
    var item = await result.ModelAsync();
```
**Unreal Engine**
```cpp

    const auto Domain = Gs2->Account->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->PlatformId(
        0, // type
        "123456" // userIdentifier
    );
    const auto Future = Domain->FindPlatformUser(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;
    const auto Result = Future2->GetTask().Result();
```


#### 登録済みプラットフォームID一覧



**Unity**
```csharp

    var items = await gs2.Account.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).PlatformIdsAsync(
    ).ToListAsync();
```
**Unreal Engine**
```cpp

    const auto It = Gs2->Account->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        GameSession
    )->PlatformIds();
    TArray<Gs2::UE5::Account::Model::FEzPlatformIdPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }
```


### カスタムスクリプトトリガー

アカウント作成や認証、引き継ぎ、BAN/解除などの処理の前後に GS2-Script を呼び出すイベントトリガーを設定できます。ゲーム固有の検証や監査を行う際に活用できます。トリガーは同期・非同期の実行方式を選択でき、非同期処理では GS2-Script や Amazon EventBridge を利用した外部連携も可能です。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

- `createAccountScript`（完了通知: `createAccountDone`）: アカウント作成の前後
- `authenticationScript`（完了通知: `authenticationDone`）: ログイン処理の前後
- `createTakeOverScript`（完了通知: `createTakeOverDone`）: 引き継ぎ情報の登録前後
- `doTakeOverScript`（完了通知: `doTakeOverDone`）: 引き継ぎ実行の前後
- `banScript`（完了通知: `banDone`）: BAN 登録の前後
- `unBanScript`（完了通知: `unBanDone`）: BAN 解除の前後

### パスワード自動変更

ネームスペースの設定で引き継ぎ時にパスワードを自動で変更することができます。
この機能を使用することで、引き継ぎ実行時に以前のデバイスでログインできなくすることができます。

ただし、この機能は複数のデバイスによる同時ログインを完全に抑止するものではありません。
GS2-Gateway の機能を利用することで、同一ユーザーが同時にログインするのを拒否する機能があり、こちらのほうがより強固に同時ログインに対応できます。

### ユーザーデータの匿名化

Namespace の設定で differentUserIdForLoginAndDataRetention を有効にすると、ログインに使用する ユーザーID と、データ保持用の データオーナーID が別々に発行されます。
アプリが認証する際は従来どおり ユーザーID を利用し、サーバー内のユーザーデータには データオーナーID を用いることで、個人情報とゲーム内データの分離を行えます。
この機能を利用することで、データ分析を匿名化したユーザーデータを対象に行えるようになり、プライバシー保護と運営に使用するデータの収集の両立が可能となります。

### マスターデータ管理
マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。

マスターデータの種類には以下があります。

- `TakeOverTypeModel`: スロット番号ごとの引き継ぎ設定と OpenID Connect 情報

以下は OpenID Connect 連携を設定するマスターデータの JSON 例です。

```json
{
  "version": "2024-07-30",
  "takeOverTypeModels": [
    {
      "type": 0,
      "metadata": "Google",
      "openIdConnectSetting": {
        "configurationPath": "https://accounts.google.com/.well-known/openid-configuration",
        "clientId": "your-client-id",
        "clientSecret": "your-client-secret"
      }
    }
  ]
}
```

Sign in with Apple の場合は `clientSecret` の代わりに `appleTeamId` / `appleKeyId` / `applePrivateKeyPem` を指定します。詳しくは [認証サービスの登録](#認証サービスの登録) を参照してください。

マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。

#### プレイヤーごとの時刻オフセット設定

Game Server Services のアカウント情報には時刻のオフセットを持たせることが可能です。
このオフセットには未来に向けて、プレイヤーが何分間先の状態として振る舞うかを設定することが可能です。

この機能を利用すると、本番環境内でもQA担当者は1時間未来の状態でゲームを遊ばせるようなことを実現できます。
こうすることで、イベントの開始時刻になってもイベントがオープンされないといった問題にいち早く気づくことができます。

### アカウントの利用停止

ゲーム内での素行が悪いプレイヤーを利用停止にすることが可能です。
BAN 状態は理由や解除予定日時を含む `BanStatus` として複数記録でき、アカウントには BAN の有無を示すフラグも保持されます。
BAN の追加・解除前後には GS2-Script を呼び出すイベントトリガーを設定できるため、外部システムとの連携を伴う制御が行えます。

## 詳細なリファレンス
<div class="section-index"><h5><a href="../../api_reference/account">GS2-Account リファレンス</a></div></div>



