Unityから直接 AWS を操作する方法 ~ AWSCognito の認証を利用して ~

# 前書き
Unity でオンラインゲーム作りたいので、ユーザーアカウント作成とログイン機能がほしい。
ログインユーザーだけがアクセスできるデータをサーバーから取り出してゲーム内で利用できるようにしたい。

上記のやりたいことが Windows マシンと Android 端末上で確認できたので
そのサンプルの具体的な作り方を Step-by-Step で解説しますね。

私と同じこと考えている人のゼロから調査する手間を、少しでも小さくできたら良いなと思ってます。

# Unity 側の作業

Unity 使う人が AWS のことを先に学ぶと「Unityで何すればよいのか？」という疑問が残るので、先に Unity で行う作業をすべて示します。

## AWS SDK のインストール

Unity 2018.3.11f1 で新規プロジェクトを作成します。
Assets 直下に AWS_packages という名でフォルダを作ります。（名前自体は自由です）
[AWSSDK.Core](https://www.nuget.org/packages/AWSSDK.Core/) を開いて、Download package をクリックして、ダウンロードできたものを AWS_packages フォルダに配置します。
.nupkg という拡張子ですが、実際は .zip ファイルなので、リネーム or 可能ならそのまま「ここで展開する」を実行します。
様々なファイルがフォルダを埋め尽くしますが \lib\net45 フォルダ配下のみ使いますので、残りは削除します。
同じような作業を以下の package についても行います。
[AWSSDK.CognitoIdentity](https://www.nuget.org/packages/AWSSDK.CognitoIdentity/)
[AWSSDK.SecurityToken](https://www.nuget.org/packages/AWSSDK.SecurityToken/)
[AWSSDK.CognitoIdentityProvider](https://www.nuget.org/packages/AWSSDK.CognitoIdentityProvider/)
[AWSSDK.Extensions.CognitoAuthentication](https://www.nuget.org/packages/AWSSDK.Extensions.CognitoAuthentication)

ここまでの作業が完了すると Unity のプロジェクトビューは次の図の通りになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/106364/2a43ee1e-2f93-2ff4-cee4-9f7758b7ea11.png)

このままだと機能しないので Edit > Project Settings > Player を選択して Configuration 項目を確認します。
そこで .NET 4.5 を有効化するために API Compatibility Level の項目を .NET 4.x にします。
**※罠注意！ Windows と Android とビルドするプラットフォームごとに個別設定する必要がありました**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/106364/88ba26dc-6682-5f99-e15f-bfc65aa75cc5.png)

この面倒くさい作業は [Unity で .NET 4.x を使用する](https://docs.microsoft.com/ja-jp/visualstudio/cross-platform/unity-scripting-upgrade?view=vs-2017) の項目「NuGet から Unity プロジェクトにパッケージを追加する」に同じ手順が書いてあります。

## AWS Cognito の利用コード
![image.png](https://qiita-image-store.s3.amazonaws.com/0/106364/8f12c715-2a1a-b915-6145-0de695239ae7.png)

Email とパスワードを入力して Sign up ボタンを押すと、Email 宛に6ケタの認証コードが届きます。
それをConfirmation Codeに入力して Submit Auth Code ボタンを押すと ID トークンという、ログインに必要な情報が手に入ります。

というのを実装し、Android 上でも動作確認できました。

オリジナルの実装は以下の記事から持ってきました。（一部、本記事用に書き直しています）
[UnityでCognito UserPoolsを使ってサインアップ・サインインを実現する](https://dev.classmethod.jp/client-side/unity-client-side/unity-cognito-authentication/)

上図の uGUI オブジェクトを作ったら、それぞれのボタンオブジェクトに以下の MonoBehaviour をアタッチして
**※罠注意！ OnClick 関数をそれぞれのボタン押下のイベントに登録してください**

Sign up ボタンには以下のスクリプトをアタッチ＆OnClick イベントに OnClick 関数を登録します。

```cs:Signup.cs

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Amazon.CognitoIdentityProvider; // for AmazonCognitoIdentityProviderClient
using Amazon.CognitoIdentityProvider.Model; // for SignUpRequest

public class Signup : MonoBehaviour
{
    public InputField emailField;
    public InputField passwordField;
    static string appClientId = AWSCognitoIDs.AppClientId;

    public void OnClick()
    {
        var client = new AmazonCognitoIdentityProviderClient(null, Amazon.RegionEndpoint.APNortheast1);
        var sr = new SignUpRequest();
        string email = emailField.text;
        string password = passwordField.text;

        sr.ClientId = appClientId;
        sr.Username = email;
        sr.Password = password;
        sr.UserAttributes = new List<AttributeType> {
            new AttributeType {
                Name = "email",
                Value = email
            }
        };

        try
        {
            SignUpResponse result = client.SignUp(sr);
            Debug.Log(result);
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }
    }
}

```

Sign In ボタンには以下のスクリプトをアタッチ＆OnClick イベントに OnClick 関数を登録します。

```cs:Signin.cs

using System;
using UnityEngine;
using UnityEngine.UI;
using Amazon.CognitoIdentityProvider; // for AmazonCognitoIdentityProviderClient
using Amazon.Extensions.CognitoAuthentication; // for CognitoUserPool
using Amazon; // for RegionEndpoint

public class Signin : MonoBehaviour
{
    public InputField emailField;
    public InputField passwordField;
    public Text resultText;
    static string appClientId = AWSCognitoIDs.AppClientId;
    static string userPoolId = AWSCognitoIDs.UserPoolId;

    public void OnClick()
    {
        try
        {
            AuthenticateWithSrpAsync();
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }
    }

    public async void AuthenticateWithSrpAsync()
    {
        var provider = new AmazonCognitoIdentityProviderClient(null, RegionEndpoint.APNortheast1);
        CognitoUserPool userPool = new CognitoUserPool(
            userPoolId,
            appClientId,
            provider
        );
        CognitoUser user = new CognitoUser(
            emailField.text,
            appClientId,
            userPool,
            provider
        );

        AuthFlowResponse context = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
        {
            Password = passwordField.text
        }).ConfigureAwait(true);

        // for debug
        resultText.text = user.SessionTokens.IdToken;
    }
}

```

Submit Auth Code ボタンには以下のスクリプトをアタッチ＆OnClick イベントに OnClick 関数を登録します。

```cs:Confirmation.cs

using System;
using UnityEngine;
using UnityEngine.UI;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;

public class Confirmation : MonoBehaviour
{
    public InputField emailField;
    public InputField confirmationCodeField;
    static string appClientId = AWSCognitoIDs.AppClientId;

    public void OnClick()
    {
        var client = new AmazonCognitoIdentityProviderClient(null, Amazon.RegionEndpoint.APNortheast1);
        ConfirmSignUpRequest confirmSignUpRequest = new ConfirmSignUpRequest();

        confirmSignUpRequest.Username = emailField.text;
        confirmSignUpRequest.ConfirmationCode = confirmationCodeField.text;
        confirmSignUpRequest.ClientId = appClientId;

        try
        {
            ConfirmSignUpResponse confirmSignUpResult = client.ConfirmSignUp(confirmSignUpRequest);
            Debug.Log(confirmSignUpResult.ToString());
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }
    }
}

```

上記コードで登場する定数を定義するファイルはこちらです。

```cs:AWSCognitoIDs.cs

public class AWSCognitoIDs
{
    public const string UserPoolId = "ap-northeast-1_XXXXXXXXX";
    public const string AppClientId = "XXXXXXXXXXXXXXXXXXXXXXXXXX";
}

```

# AWS Console 側の準備

Amazon Web Service (AWS) の一機能 AWS Cognito を使ってログインアカウントの管理・Web Service のアクセス制御を行います。
Unity にだけ詳しい方は、急に覚えることが多くなって大変ですが、AWS もネットワークの歴史や深い技術知識を持たなくてもWebサービスが構築できる、いわゆる Unity のように 3DCG の技術知識が不足していてもゲームが作れるようなツールと考えてしまうのも良いかもしれません。

月に数千人がアクセスするような大人気ゲームにならない限り無料枠で運用できますので、小規模なテストするくらいなら金額は心配しなくて良いと思います。（お金かかっても 0.1 円くらい）

## ユーザープールを作成

サインアップ、認証コードをメールアドレスへ送信、サインインできるようになります。
外部記事に記録しました。
[AWS DynamoDB を Unity で操作する](https://simplestar-tech.hatenablog.com/entry/2019/03/21/231815)
ここを読めば、上記実装で出てきた UserPoolId と AppClientId の作り方がはっきりします。

## IDプールを作成

未認証ユーザー、サインインしたユーザーそれぞれにAWSアクセス権限を付与します。
こうすることでアクセス制御が細かく設定できるようになります。

これも先ほどの外部記事に連続して記録しました。
[AWS DynamoDB を Unity で操作する](https://simplestar-tech.hatenablog.com/entry/2019/03/21/231815)
ここを読めば、IAM（あいあむ）ポリシーの操作が明確になって、主観ですが実装者として安心できるようになると思います。

# AWS SDK サンプルを確認する

DynamoDB を Unity から操作するサンプルが AWS から公開されていますので、そちらを触っておきます。

## Unity から AWS DynamoDB を利用する

これも先ほどの外部記事に連続して記録しました。
[AWS DynamoDB を Unity で操作する](https://simplestar-tech.hatenablog.com/entry/2019/03/21/231815)
ここを読めば DynamoDB に任意のデータ構造で値を挿入したり、更新したりの実装が、理解できるようになります。

**ただ、この記事は困ったことに未認証での AWS の操作までしか示されていません。**

## 認証済みユーザーとして AWS を利用する

外部記事で紹介した Unity 向け AWS DynamoDB サンプルと
ここまで解説してきた上記ログインサンプルを統合します。

Unity 向けの AWSSDK は削除し、例によって
[AWSSDK.DynamoDBv2](https://www.nuget.org/packages/AWSSDK.DynamoDBv2/)
よりパッケージをダウンロードして展開し、この展開パッケージを利用して DynamoDB を操作できるようにします。

DynamoDB を操作する API が交換されるため、サンプルコードの AWS SDK の関数のコールバック関数登録の引数を省略するように何か所も修正していきますが、この作業がわかる人が記事を読んでくれているとうれしいです。 
手順化する文章を作ることが困難なため、ここは詳しく解説しません。（頑張って！）

そして、上記ログインサンプルで得た ID トークンを次のように CognitoAWSCredentials インスタンスの AddLogin 関数に渡すよう実装します。

```cs
        // for debug
        resultText.text = user.SessionTokens.IdToken;
        AWSCognitoIDs.IDToken = user.SessionTokens.IdToken;
        var credentials = db.Credentials as CognitoAWSCredentials;
        credentials.AddLogin($"cognito-idp.{AWSCognitoIDs.CognitoPoolRegion}.amazonaws.com/{AWSCognitoIDs.UserPoolId}", AWSCognitoIDs.IDToken);
```

統合に際し、次のようにグローバル参照定数の方を追記して利用しています。

```cs
using Amazon;

public class AWSCognitoIDs
{
    public const string IdentityPoolId = "ap-northeast-1:00000000-xxxx-0000-xxxx-000000000000";
    public static string CognitoPoolRegion = RegionEndpoint.APNortheast1.SystemName;
    public static string DynamoRegion = RegionEndpoint.APNortheast1.SystemName;
    public const string UserPoolId = "ap-northeast-1_XXXXXXXXX";
    public const string AppClientId = "XXXXXXXXXXXXXXXXXXXXXXXXXX";
    public static string IDToken = "invalid default";
}
```

## 動作試験と結果

IAM ポリシーの方で、未認証ロールに DynamoDB を触らせないようにし、認証済みロールに対して DynamoDB を触らせる権限を与える修正を行いました。

未認証で Create Table などが全く機能しなくなり、Sign In 後に Create Table や Delete Table が機能することを確認しました。

# まとめ

本記事のタイトル「UnityでAWSCognito の認証を利用して AWS を操作した」を現実のものとしました。
Unity ゲーム開発において、アカウント作成、ログイン、認証ユーザーのみ権限を付与といった当たり前のことが難なくできるようになって
より多くの人がゲームの面白さの部分に注力できるようになればと思います。

ここまで読んでくださりありがとうございました。

## 記事作成にあたって参考にした関連Qiita記事一覧

[UnityからAWS Cognito Identity Providerで認証機能を実装する（User_Auth_Flow）](https://qiita.com/t5ujiri/items/07575a7596e18340eefb)
[UnityでAWSを使うのはゼンゼンコワクナイヨー（シカモ無料デイケルヨ！）](https://qiita.com/Ijoru/items/8cdd49ef31bed7cafdac)