Cognito IDプールを使用したお問い合わせフォームの作成(認証されないアクセス)

Cognito IDプールをお問い合わせフォームを作りながら使いながら理解する。パブリックプロバイダーは使用せず認証されない場合の動作のみを対象としています。
2018.09.09

はじめに

おはようございます、加藤です。気づけば入社して8ヶ月目でした、最近時間の流れが早いです。
今回は、CognitoのIDプールを使用した一時的な認証情報の取得とサンプルコードを紹介します。
パブリックログインプロバイダー(Facebook、Google+、Login with Amazon など)は使用しません、認証されていないアクセスが対象です。
それと今回はCognito IDプールのみの内容ですユーザープールは一切使用しません(自分は勉強する時にかなり混乱しました...)。

認証されていないアクセスのフロー

最初にCognitoで認証されていないアクセスのフローを説明します。

Deviceが最終的に欲しいものはSTSによって発行される一時的な認証情報です。
STSへのアクセスはCognitoが行うので、DeviceはCognitoにのみアクセスします。
Cognitoは認証される/されないによって、取得する認証情報(IAMロール)を分けることができます。

今回作るもの

作成するものはお問い合わせフォームです。
Webサイトにアクセスし、タイトルと内容を記載して送信ボタンを押すと、記載内容がデータ保存用バケットにJSONで保存されるという仕組みを作ってみます。

やってみた

S3バケットの作成

Webサイト用とデータ保存用のS3バケットを作成します。

Webサイト用

バケットポリシー

パブリックアクセスを許可するためにアクセス権限の項目でバケットポリシーを設定します。

bucket_policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<<YOUR_WEBSITE_BUCKET_NAME>>/*"
        }
    ]
}
Static website hosting

Webサイトとして使用するために、プロパティの項目でStatic website hostingを有効にします。インデックスドキュメントはindex.htmlに設定します。
後で使用するので、エンドポイントをメモしておいてください。

データ保存用

CORSの設定

Webサイト用とデータ保存用のバケットを分けている為、クロスドメイン通信が必要です。
バケットへのPUTを許可するCORS(CrossDomainResourceSharing)を設定します。
ここで先程メモしたWebサイト用バケットのエンドポイントを使用します。

cors.xml

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin><<YOUR_WEBSITE_BUCKET_ENDPOINT>></AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Cognito IDプールの作成

ID プールを作成します。プール名は任意の名前でOKです。
認証されていないIDに対してアクセスを有効にするにチェックを入れて作成します。

作成すると、Auth/Unauthに割り当てるIAMロールの設定を求められます。
デフォルトの新規作成のまま進めます。Unauthのロール名は後でポリシーを割り当てるのに使用するのでメモしておきます。

作成が完了すると認証情報取得の為のサンプルコードが表示されます。
IDプールのIDをメモしておきましょう。

UnauthのIAMロールへポリシーの割り当て

UnauthのIAMロール(IDプール名がsampleならCognito_sampleUnauth_Role)にデータ保存用バケットへのアクセス権限を与えます。

policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::<<YOUR_DATA_BUCKET_NAME>>/*"
        }
    ]
}

Webサイトの作成

Webサイトを作成します。

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.311.0.min.js"></script>
    <title>Sample</title>
    <script>
        var $id = function (id) { return document.getElementById(id);};
        AWS.config.region = "ap-northeast-1"
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: '<<YOUR_IDENTITY_POOL_ID>>',
            });

        function uploadFile() {
            var s3BucketName = "<<YOUR_DATA_BUCKET_NAME>>"
            var now = new Date();
            var obj = {"title":$id("title").value, "contents":$id("contents").value, "date": now.toLocaleString()};
            var s3 = new AWS.S3({params: {Bucket: s3BucketName}});
            var blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});
            s3.putObject({Key: "uploads/"+now.getTime()+".txt", ContentType: "text/plain", Body: blob, ACL: "public-read"},
            function(err, data) {
                if (data !== null) {
                    alert("アップロード成功");
                }
                else {
                    alert("アップロード失敗:" + err.message);
                }
            });
        }
    </script>
</head>
<body>
    <div>
        <div>
            <form>
                <table>
                    <tr>
                        <th>タイトル</th>
                        <td>
                            <input id ="title" type="text" name="title" class="titletext fontchange" maxlength="40" value="" />
                        </td>
                    </tr>
                    <tr>
                        <th>内容</th>
                        <td>
                            <TEXTAREA id="contents" cols="40" rows="6" name="contents" class="fontchange"></TEXTAREA>
                        </td>
                    </tr>
                </table>
                <div id="button_area" class="clearfix">
                    <input onclick="uploadFile();" type="button" value="送信" id="css_button" class="button_right" />
                </div>
            </form>
        </div>
    </div>
</body>
</html>
<!---参考元: https://www.i-enter.co.jp/blog/blog/2015/02/03/s3/--->

作成したindex.htmlをWebサイト用のバケットのルートにアップロードします。

動作テスト

メモしておいたWebサイト用バケットのエンドポイントにブラウザでアクセスします。

タイトルと内容を適当に入力して送信ボタンを押します、アップロード成功と表示されればOKです。
データ保存用バケットを確認してみましょう。
uploadsフォルダの中にテキストファイルが作成されており、下記の様になっていれば最終確認OKです。

uploads/xxxxx.txt

{
  "title": "aaa",
  "contents": "bbbbb",
  "date": "2018/9/9 10:19:27"
}

参考

あとがき

Cognito IDプールについて認証される場合のフローは情報が沢山あったのですが、認証されない場合のフロー情報がすぐに見つからず苦労しました。
最初の載せたアクセスのフローは自分で作ったものなので間違いや追加した方が良いものがあればコメントお願いします。