【AWS】Amazon Cognito〜ログイン画面(HTML/Javascript)のハンズオン〜

# はじめに
今回はAWSドキュメントを見ながらAmazon Cognitoを触ってみました。
SAP勉強中で、GWで時間もできたことなので実際に手を動かしてディープに理解していきます。

# Amazon Cognitoとは
- アプリにユーザ認証機能を提供するサービス。
- モバイルアプリやWebアプリに、ユーザーのサインイン・認証機能・ユーザー管理機能を簡単に実装できる。

# コンポーネント
Amazon Cognitoには２つのコンポーネントが存在する。
２つの違いが初見だと理解に苦しみましたが、**誰にどんな認証/認可機能を提供するか**を整理して考えるとスッキリしました。

## 1.ユーザープール（認証処理）
- ユーザープールは、Amazon Cognito のユーザーディレクトリ
- ユーザーはモバイルアプリ・Webアプリに、Cognito経由でサインインしたり、サードパーティ（Twitter,Google,Facebookなど)経由でユーザ認証が可能
- MFA（多要素認証）、Eメールや電話番号による検証が可能
- 正常に認証されるとCognitがユーザーに対してJWTトークンを発行する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/c926c247-102d-0c8d-db38-70e010a896b5.png)
※AWS BlackBeltから抜粋

## 2.IDプール（認可処理)
- ユーザーに一意のIDを発行して、IDプロバイダーでフェデレートすることが可能。
- IDプールを使用すると、一時的なAWS認証情報を取得して、様々なAWSサービスにアクセスすることができる。
- 以下のIDプロバイダーをサポートしている
  -  パブリックプロバイダー(Amazon,Facebook,Google,Apple)
  -  Cognitoユーザープール
  -  Open ID Connect
  -  SAML ID
  -  開発者が認証したアイデンティティ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/c38c66f6-bbc1-d56b-a6f5-d55d872c9a76.png)
※AWS BlackBeltから抜粋

# ハンズオン
今回は簡易的なログインページを作成してみました。
今回はCognitoの動作確認が目的なので、デザインのこだわりは無いのと、フレームワークなしのHTML、JavaScriptを使って実装しました。
実装は[こちら](https://qiita.com/fkooo/items/660cab0090a80861155b#%E5%AE%8C%E6%88%90%E7%94%BB%E9%9D%A2)を参考にしました。（とてもわかりやすかったです。。）
ハンズオンのフローとしては、
メールアドレスとパスワードを使ってログイン->Eメールに認証情報が届く->認証情報を入力->ログイン成功することを確認したいと思います。

## Cognitoのユーザープールの作成
- AWSコンソールにログイン>サービス>Cognitoを選択
- 右側の「ビジネスケースから始める」でアプリケーションにユーザーディレクトリを作成する。を選択する。（２０２３年５月現在の画面）
「ユーザープールを作成」を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/9d1b6c5c-c790-205c-bc7d-e4b18ef0e9e3.png)

- 今回はEメールで検証したいので「Eメール」にチェックをつけます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/a341af0b-c640-aa5e-e064-f8e8862a31f1.png)

- パスワードポリシーは特にこだわりがないので、デフォルトのままにします。
アプリによってパスワードポリシーが異なればカスタム設定ができるということですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/1d084e55-66d4-e839-350c-4ac5ef5f2bcd.png)

- MFA（多要素認証）は「なし」で設定します。
サインインで、二段階認証としてSMS認証や認証アプリなど設定することができそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/2da75a6d-669d-28a3-6480-ff6b7d696886.png)

- アカウントの復旧は無効化に設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/cc6e1dac-c7ba-7cd3-ca00-eaddfd59f38f.png)

- セルフサービスのサインアップはデフォルトで自己登録を有効化してます。
無効化すると、sign-upリクエストが拒否されるとか、、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/8fee636d-c928-450c-530f-6a8cdd140ef9.png)

- できるだけCognitoにお任せしたいので、Cognitoアシスト型は許可してます。
検証属性はEメールの検証なので、Eメールを設定してます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/b456dd54-ea7a-392b-db35-ddd5de7a5242.png)

- アカウント作成時にEメールアドレスが必須の想定なので、email属性を指定します。
カスタム属性は指定しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/e9b0c1c0-f344-1a65-47bc-95c22836b322.png)

- Eメールの送信方法を指定します。
今回はCognitoを選びました。１日最大５０通のメール送信ができるとの記載があるので、検証には十分です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/0a7ad258-01eb-9370-bc0b-3674986524f4.png)

- ユーザープール名を設定します。（プール名いつ設定するのかな？と思ったらここで出てきて安心しました。）
OAuth2.0を使用しないので、ホストされた認証ページは無効化としてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/95906151-e73a-6828-19f6-019b8d57cd70.png)

- アプリケーションクライアントを設定します。
クライアントのシークレットは今回生成しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/d2979e2b-937c-9654-64ca-87692cfb98ce.png)

- 確認画面->「ユーザープールの作成」->プールが作成できていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/f149e980-f4e3-67e5-b03a-a100d1e73163.png)

ここまででユーザープールの作成は完了ですが、
最後に「ユーザープールID」の値が必要になるのでメモしておきます。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/1fef2ac5-edd0-b8b8-4d8f-716cc69b09d9.png)
アプリケーションクライアントの「クライアントID」も後ほど必要になりますのでこれもメモします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/52328e78-25e5-f95b-423a-1cca9331f0e3.png)

## CognitoのIDプールの作成
- ユーザープールの右メニューに「フェデレーティッドID」を選択します。
ここからIDプールを作成することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/0377f377-0a67-f636-05ab-af2f9be48a16.png)

-IDプール名を設定します。（入力後、緑のチェックマークが表示されればOK）
認証プロバイダーを「Cognito」を選択し、先ほどメモしたIDを入力して「プールの作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/a3f8e777-072d-ff06-0b20-5073f74f509e.png)

- IAMロールが自動的に作成されるので、「許可」を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/0842ede6-774c-94d3-c56a-966ac1ef1429.png)

これでIDプールの作成は完了ですが、
AWS認証情報の取得を後ほど使用するので、こちらもメモを取っておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/16a3337c-486d-373f-ef4b-d53175da9e32.png)

## サインイン画面の実装
３つの画面を用意します。

``` signup.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  </meta>
  <title>CognitoSignUp</title>
  <script src="js/aws-cognito-sdk.min.js"></script>
  <script src="js/amazon-cognito-identity.min.js"></script>
  <script src="js/main.js"></script>
</head>

<body>
  <h1>KitonSite サインイン画面</h1>
  <span style="display: inline-block; width: 100px;">Email</span>
  <input type="text" id="email" placeholder=" メールアドレス">
  <br />
  <span style="display: inline-block; width: 100px;">Password</span>
  <input type="password" id="password" placeholder="パスワード">
  <br />
  <input type="button" value="サインアップ" onclick="SignUp();">
</body>

</html>
```


``` confirm.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  </meta>
  <title>CognitoConfirmRegistration</title>
  <script src="js/aws-cognito-sdk.min.js"></script>
  <script src="js/amazon-cognito-identity.min.js"></script>
  <script src="js/main.js"></script>
</head>

<body>
  <h1>KitonSite 認証画面</h1>
  <p>入力したメールアドレスに認証コードを送付しました。</p>
  <p>メールアドレスと認証コードを入力してください。</p>
  <span style="display: inline-block; width: 100px;">Email</span>
  <input type="text" id="email" placeholder="メールアドレス">
  <br />
  <span style="display: inline-block; width: 100px;">ConfirmCode</span>
  <input type="text" id="ConfirmCode" placeholder="認証コード">
  <br /><br />
  <input type="button" value="認証" onclick="ConfirmRegistration();">
</body>

</html>
```

``` login.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  </meta>
  <title>CognitoAuthenticateUser</title>
  <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>
  <script src="js/main.js"></script>
</head>

<body>
  <h1>KitonSite ログイン画面</h1>
  <span style="display: inline-block; width: 100px;">Email</span>
  <input type="text" id="email" placeholder="メールアドレス">
  <br />
  <span style="display: inline-block; width: 100px;">Password</span>
  <input type="password" id="password" placeholder="パスワード">
  <br /><br />
  <input type="button" value="ログイン" onclick="LoginUser();">
</body>

</html>
```

- Javascriptは１つのファイルにまとめて書きました。（リファクタリングできてません。。）

```main.js

const poolData = {
	UserPoolId: 'us-east-1_xxxxxx', // ユーザープール固有のID（メモした値）
	ClientId: 'xxxxyyyyzzzz', // クライアント固有のID（メモした値）
};

// signup.html
function SignUp() {
	var username = document.getElementById("email").value;
	var password = document.getElementById("password").value;
	var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

	userPool.signUp(username, password, null, null, function(
		err,
		result
	) {
		if (err) {
		alert(err.message || JSON.stringify(err));
			return;
		}
		var cognitoUser = result.user;
		console.log('user name is ' + cognitoUser.getUsername());
		window.location.href = 'confirm.html';
	});
}

// confirm.html
function ConfirmRegistration() {
	var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
	var username = document.getElementById("email").value;
	var code = document.getElementById("ConfirmCode").value;
	var userData = {
		Username: username,
		Pool: userPool,
	};
	var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

	cognitoUser.confirmRegistration(code, true, function(err, result) {
		if (err) {
			alert(err.message || JSON.stringify(err));
			return;
		}
		console.log('call result: ' + result);
		window.location.href = 'login.html';		
	});
}

// login.html
function LoginUser() {

	var username = document.getElementById("email").value;
	var password = document.getElementById("password").value;

	var authenticationData = {
		Username: username,
		Password: password,
	};

	var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
		authenticationData
	);
	var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
	var userData = {
		Username: username,
		Pool: userPool,
	};

	var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
	cognitoUser.authenticateUser(authenticationDetails, {
		onSuccess: function(result) {
            var idToken = result.getIdToken().getJwtToken();          // IDトークン
			var accessToken = result.getAccessToken().getJwtToken();  // アクセストークン
            var refreshToken = result.getRefreshToken().getToken();   // 更新トークン
            
            console.log("idToken : " + idToken);
            console.log("accessToken : " + accessToken);
            console.log("refreshToken : " + refreshToken);

			AWS.config.region = 'us-east-1';

			AWS.config.credentials = new AWS.CognitoIdentityCredentials({
				IdentityPoolId: 'us-east-1:xxxx-yyyy-zzzz', // IDプールの値（メモした値）
				Logins: {
					// Change the key below according to the specific region your user pool is in.
					'cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxx': result
						.getIdToken()
						.getJwtToken(),
				},
			});

			AWS.config.credentials.refresh(error => {
				if (error) {
					console.error(error);
				} else {
					console.log('Successfully logged!');
				}
			});
		},

		onFailure: function(err) {
			alert(err.message || JSON.stringify(err));
		},
	});
}
```
# 動作確認

- サインアップを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/36a7ef94-67c1-1a24-69ee-ff36d86c9cda.png)

- 認証コードがメールアドレスに届きました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/c45d5272-8767-a963-4741-ce05f35ccfb7.png)

- Cognitoのコンソール画面を確認すると、ユーザが１人追加されてますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/f853b164-06b0-f503-a95c-ac9857acb6a4.png)

- メールアドレスと認証コードを入力して認証してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/d0539934-9fda-de22-ff37-cd4460d21073.png)

- コンソールでsuccess!が表示できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/4df5f5ea-6e17-79fe-b42e-ab89cd049f1f.png)

- Cognitoコンソールを確認すると、確認ステータスが「確認済み」となりました！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/2a746f6c-282d-1415-2788-84a9807db61e.png)

- 最後にIDプールで認証できるかログインしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/24a8b6e7-446c-9204-f810-29238a8eba4c.png)

- IDの合計が増えましたね！（記事書く前に動作確認をしたので合計が２になってますが、本来は１加算されているはずです。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225450/04ad0ac2-22d4-b97f-c574-ca300f8df954.png)


# 最後に
とっても長くなりましたが、今回はCognitoのハンズオンを実践してみました。
実際に手を動かしてみた感想としては、ユーザプールの設定に少し時間がかかりましたが、細かな設定ができるので様々な要件にも対応できそうだなと感じました。

ただ、気になることも多々あるので、SAP勉強が落ち着いたらもっと検証してみたいですね！！
例えば、
- ユーザー認証を自前で実装しているアプリケーションをCognitoに移行可能かどうか
- signupしたユーザが事前に設定したグループに自動的に振り分けられるかどうか
- サードパーティ経由でユーザー認証
- 他のAWSサービスとの連携(API Gateway,Amplify,Lambdaなど、、、）

# 参考サイト
https://d1.awsstatic.com/webinars/jp/pdf/services/20200630_AWS_BlackBelt_Amazon%20Cognito.pdf

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html

https://aws.amazon.com/jp/cognito/faqs/

https://qiita.com/fkooo/items/660cab0090a80861155b#%E5%AE%8C%E6%88%90%E7%94%BB%E9%9D%A2