Javascriptのシンプルな構成でAWS Cognitoを理解する

#概要
いろいろと理解しなければならないことが多いですが、まずは、できるだけシンプルな構成でAWS Cognitoの基礎を理解します。
jQueryは使わずにJavascriptのみです。
ただし、アカウントの属性には標準属性とカスタム属性を設定します。
今回説明する内容を踏まえて、実用ではAmplifyを使うと良いかと思っています。

[こちらを参考にしました。](https://www.tdi.co.jp/miso/amazon-cognito-activation-sign-in)

#画面遷移
まずはサインアップ画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/f834150a-8418-821a-b608-a3c274e34234.png)
サインアップするとメールで検証リンクが配信されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/bb6ff189-e0ff-de1f-ec2b-c618f5775d35.png)
`Verify Email`リンクをクリックすると検証が完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/fbb36d81-b66b-6599-cc36-8e42f5170cc8.png)

次にサインイン画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/cee86e2d-b139-53c9-2857-a9919d5b4517.png)
最後に認証されたアカウントしか入ることのできない画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/ac4086f7-83b8-9215-df69-5a6b0eeafa23.png)
`Sign Out`をクリックするとサインアウトしてサインイン画面に遷移します。

#ファイル構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/21a4b732-543f-fdff-85f8-eede17cc78fb.png)

#jsフォルダへ準備するライブラリ

+ [amazon-cognito-identity.min.js](https://raw.githubusercontent.com/amazon-archives/amazon-cognito-identity-js/master/dist/amazon-cognito-identity.min.js)
+ [aws-cognito-sdk.min.js](https://raw.githubusercontent.com/amazon-archives/amazon-cognito-identity-js/master/dist/aws-cognito-sdk.min.js)
+ [aws-sdk.min.js](https://raw.githubusercontent.com/aws/aws-sdk-js/master/dist/aws-sdk.min.js)
+ [jsbn.js](http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js)
+ [jsbn2.js](http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn2.js)
+ [sjcl.js](https://raw.githubusercontent.com/bitwiseshiftleft/sjcl/master/sjcl.js)

それぞれのREAD.MEをしっかり確認してください。

#AWS Cognitoの設定
1. AWSへサインイン
2. Cognitoのコンソールへ
3. リージョンを東京へ変更<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/ec57492e-eca6-c78e-d0db-2160cd52758f.png)
4. `ユーザープールの管理`をクリック
5. `ユーザープールを作成する`をクリック<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/c67a809b-ffec-e74f-e614-80d441c0be01.png)
6. `プール名`へお好きな名前を付けてください。<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/c4178fd6-51f0-e419-225c-43fd723119ec.png)
7. `デフォルトを確認する`をクリックすると↓の画面になります。<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/d56791dc-cff5-5dcb-5373-ef6904e1c52f.png)
8. サイドバーの`属性`をクリック
9. 今回は↓のように設定します。<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/acb966dc-acc4-d0c9-82a8-be411d574a71.png)<br>カスタム属性には`role`と入力しますが、自動で`custom:role`と変更されます。
10. `次のステップ`をクリック
11. 「ポリシー」の設定<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/2901e16f-bbd5-4537-715e-398a70c5f908.png)
12. 「MFAそして確認」の設定<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/0397594c-2b3c-94d3-0eb4-39386937892a.png)
13. 「メッセージのカスタマイズ」の設定<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/3a49e01e-1838-3b35-ef48-3ca0dfd7634e.png)
14. 「アプリクライアント」の設定で`アプリクライアントの追加`をクリック<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/c36555de-7069-a96e-c335-87667b60674a.png)
15. アプリクライアント名を入力し、`クライアントシークレットを作成`のチェックを外す<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/2f02f520-8266-9e6b-a966-18eb22727e17.png)
16. サイドバーで「確認」をクリックすると↓が表示され、`プールの作成`をクリック<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/7f994729-72fb-10e9-19e3-25553f892453.png)
17. プールIDが表示されるので、それをメモ<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/120e7736-b1d4-d563-4f5b-ceb52c51ca4e.png)
18. サイドバーから「アプリクライアント」を選択すると`アプリクライアントID`が表示されるので、それをメモ<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/46134fe5-55b1-0c48-9cba-16b5b49f2289.png)
19. サイドバーから「ドメイン名」を選択するとAmazon Cognito ドメインの作成ができるので好きな名前を入力してドメインを作成します。<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/56547047-e2be-25df-8954-e57c8e7f7ac6.png)
20. これでユーザープールの設定が完了

#フェデレーティッドアイデンティティの設定

1. Cognitoコンソールの上部にある`フェデレーティッドアイデンティティ`をクリック<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/7f491f46-4a04-4b37-3c72-cbad0243ce6e.png)
2. ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/74b18592-0045-567e-20e6-4746b3d4a69e.png)をクリック
3. 使用開始ウィザードでIDプール名に好きな名前を入力
4. `認証されていないIDに対してアクセスを有効にする`をチェック
5. 認証プロバイダーを展開し、Cognitoを選択
6. メモしたユーザープールIDとアプリクライアントIDを入力<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/07596dbd-4ab4-10fe-5998-04a43268f547.png)
7. プールの作成をクリック
8. 次の画面ではデフォルトの内容を確認して、`許可`をクリック
9. サイドバーでサンプルコードを選択し、プラットフォームを`Javascript`を選択。AWS認証情報の取得に表示されているコードをメモ<br>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/8bd3cebf-f7de-1d08-2efc-8e1e29cb457c.png)
10. これでCognitoの設定が完了

#サインアップページの作成
```html:signup.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Sign Up</title>
    <script src="./js/jsbn.js"></script>
    <script src="./js/jsbn2.js"></script>
    <script src="./js/sjcl.js"></script>
    <script src="./js/aws-sdk.min.js"></script>
    <script src="./js/aws-cognito-sdk.min.js"></script>
    <script src="./js/amazon-cognito-identity.min.js"></script>
  </head>
  <body>
    <div id="signup">
      <h1>Sign Up</h1>
      <div id="message"><span id="message-span" style="color: red;"></span></div>
      <form name="form-signup">
        <span style="display: inline-block; width: 150px;">User ID(Email)</span>
        <input type="text" id="email" placeholder="Email Address" />
        <br />
        <span style="display: inline-block; width: 150px;">Name</span>
        <input type="text" id="name" placeholder="Name" />
        <br />
        <span style="display: inline-block; width: 150px;">Password</span>
        <input type="password" id="password" placeholder="Password" />
        <br /><br />
        <input type="button" id="createAccount" value="Create Account" />
      </form>
    </div>
    <br />
    <a href="./signin.html">Sign In!</a>
    <script src="./js/signup.js" defer></script>
  </body>
</html>
```

#サインアップの処理
これまでにメモした`ユーザープールID`、`クライアントID`、`AWS認証情報`を張り付けます。

```javascript:js/signup.js
(() => {
  // ユーザープールの設定
  const poolData = {
    UserPoolId: "us-east-1_xxxxxxxx",
    ClientId: "xxxxxxxxxxxxxxxxxxxxx"
  };
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  const attributeList = [];

  // Amazon Cognito 認証情報プロバイダーを初期化します
  AWS.config.region = "us-east-1"; // リージョン
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  });

  // 「Create Account」ボタン押下時
  const createAccountBtn = document.getElementById("createAccount");
  createAccountBtn.addEventListener("click", () => {
    /**
     * サインアップ処理。
     */
    const username = document.getElementById("email").value;
    const name = document.getElementById("name").value;
    const password = document.getElementById('password').value;

    // 何か1つでも未入力の項目がある場合、処理終了
    const message = document.getElementById("message-span");
    if (!username | !name | !password) {
      message.innerHTML = "未入力項目があります。";
      return false;
    }

    // ユーザ属性リストの生成
    const dataName = {
      Name: "name",
      Value: name
    };
    const dataRole = {
      Name: "custom:role",
      Value: "5"
    };
    const attributeName = new AmazonCognitoIdentity.CognitoUserAttribute(
      dataName
    );
    const attributeRole = new AmazonCognitoIdentity.CognitoUserAttribute(
      dataRole
    );

    attributeList.push(attributeName);
    attributeList.push(attributeRole);

    // サインアップ処理
    userPool.signUp(username, password, attributeList, null, (err, result) => {
      if (err) {
        message.innerHTML = err.message;
        return;
      } else {
        // サインアップ成功の場合、アクティベーション画面に遷移する
        alert(
          "登録したメールアドレスへアクティベーション用のリンクを送付しました。"
        );
        location.href = "signin.html";
      }
    });
  });
})();
```

#サインインページの作成
```html:signin.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Sign In</title>
    <script src="./js/jsbn.js"></script>
    <script src="./js/jsbn2.js"></script>
    <script src="./js/sjcl.js"></script>
    <script src="./js/aws-sdk.min.js"></script>
    <script src="./js/aws-cognito-sdk.min.js"></script>
    <script src="./js/amazon-cognito-identity.min.js"></script>
  </head>
  <body>
    <div id="signin">
      <h1>Sign In</h1>
      <div id="message"><span id="message-span" style="color: red;"></span></div>
      <form name="form-signin">
        <span style="display: inline-block; width: 150px;">Email Address</span>
        <input type="text" id="email" placeholder="Email Address" />
        <br />
        <span style="display: inline-block; width: 150px;">Password</span>
        <input type="password" id="password" placeholder="Password" />
        <br /><br />
        <input type="button" id="signinButton" value="Sign In" />
      </form>
    </div>
    <br />
    <a href="./signup.html">Sign Up!</a>
    <script src="./js/signin.js" defer></script>
  </body>
</html>
```

#サインインの処理
これまでにメモした`ユーザープールID`、`クライアントID`、`AWS認証情報`を張り付けます。

```javascript:js/signin.js
(() => {
  // ユーザープールの設定
  const poolData = {
    UserPoolId: "us-east-1_xxxxxxxx",
    ClientId: "xxxxxxxxxxxxxxxxxxxxxxxxx"
  };
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

  // Amazon Cognito 認証情報プロバイダーを初期化します
  AWS.config.region = "us-east-1"; // リージョン
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  });

  /**
   * サインイン処理
   */
  document.getElementById("signinButton").addEventListener("click", () => {
    const email = document.getElementById('email').value;
    const password = document.getElementById('password').value;

    // 何か1つでも未入力の項目がある場合、メッセージを表示して処理を中断
    const message = document.getElementById('message-span');
    if (!email | !password) {
      message.innerHTML = "All fields are required.";
      return false;
    }

    // 認証データの作成
    const authenticationData = {
      Username: email,
      Password: password
    };
    const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
      authenticationData
    );

    const userData = {
      Username: email,
      Pool: userPool
    };
    const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

    // 認証処理
    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: result => {
        const idToken = result.getIdToken().getJwtToken(); // IDトークン
        const accessToken = result.getAccessToken().getJwtToken(); // アクセストークン
        const refreshToken = result.getRefreshToken().getToken(); // 更新トークン

        console.log("idToken : " + idToken);
        console.log("accessToken : " + accessToken);
        console.log("refreshToken : " + refreshToken);

        // サインイン成功の場合、次の画面へ遷移
        location.href = "index.html";
      },

      onFailure: err => {
        // サインイン失敗の場合、エラーメッセージを画面に表示
        console.log(err);
        message.innerHTML = err.message;
      }
    });
  });
})();
```

#認証完了で閲覧できるページの作成
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Menu</title>
    <script src="./js/jsbn.js"></script>
    <script src="./js/jsbn2.js"></script>
    <script src="./js/sjcl.js"></script>
    <script src="./js/aws-sdk.min.js"></script>
    <script src="./js/aws-cognito-sdk.min.js"></script>
    <script src="./js/amazon-cognito-identity.min.js"></script>
  </head>
  <body>
    <div id="menu">
      <h1 id="name"></h1>
      <h2 id="role"></h2>
      <p id="email"></p>
    </div>
    <button id="signout" hidden>Sign Out</button>
    <script src="./js/index.js" defer></script>
  </body>
</html>
```

#認証済みかチェックする処理
これまでにメモした`ユーザープールID`、`クライアントID`、`AWS認証情報`を張り付けます。

```javascript:js/index.js
(() => {
  // ユーザープールの設定
  const poolData = {
    UserPoolId: "us-east-1_xxxxxxxxxx",
    ClientId: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
  };
  const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
  const cognitoUser = userPool.getCurrentUser(); // 現在のユーザー

  const currentUserData = {}; // ユーザーの属性情報

  // Amazon Cognito 認証情報プロバイダーを初期化します
  AWS.config.region = "us-east-1"; // リージョン
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: "us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  });

  // 現在のユーザーの属性情報を取得・表示する
  // 現在のユーザー情報が取得できているか？
  if (cognitoUser != null) {
    cognitoUser.getSession((err, session) => {
      if (err) {
        console.log(err);
        location.href = "signin.html";
      } else {
        // ユーザの属性を取得
        cognitoUser.getUserAttributes((err, result) => {
          if (err) {
            location.href = "signin.html";
          }

          // 取得した属性情報を連想配列に格納
          for (i = 0; i < result.length; i++) {
            currentUserData[result[i].getName()] = result[i].getValue();
          }
          document.getElementById("name").innerHTML =
            "ようこそ！" + currentUserData["name"] + "さん";
          document.getElementById("role").innerHTML =
            "Your Role is " + currentUserData["custom:role"];
          document.getElementById("email").innerHTML =
            "Your E-Mail is " + currentUserData["email"];

          // サインアウト処理
          const signoutButton = document.getElementById("signout");
          signoutButton.addEventListener("click", event => {
            cognitoUser.signOut();
            location.reload();
          });
          signoutButton.hidden = false;
          console.log(currentUserData);
        });
      }
    });
  } else {
    location.href = "signin.html";
  }
})();
```

#動作のテスト
##一連の流れをブラウザでテスト
1. サインアップ
2. メールでアクティベート
3. サインイン
4. 認証情報の表示
5. サインアウト

##Cognitoのコンソールでユーザーの状況が確認できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/4481aeb6-f9c6-93c1-8a3e-63773b724baf.png)

##ユーザーを無効化したり、削除したり、詳細な操作ができる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208266/e0c15b37-8d87-99b1-72da-f1a1136a660b.png)

#最後に
他にもパスワードの変更や再設定など必要な機能があるかと思いますが、入門としてはシンプルに作成できたかと思います。
ここからJWTや認証・認可などへ広げていけば良いかと思います。

#参考

[Amazon Cognitoを使ったサインイン画面をつくってみる～アクティベーション＆サインイン編～](https://www.tdi.co.jp/miso/amazon-cognito-activation-sign-in)