【AWS】Cognito + Amplifyでオリジナルのサインアップ・サインイン・サインアウト画面を作成する

# はじめに

ユーザ管理基盤の実装というのはいつだって考えることや懸念するセキュリティリスクが多くてめんどくさい。。。

というわけでAWSが提供するフルマネージドなユーザ基盤の[Cognito(コグニート)](https://aws.amazon.com/jp/cognito/)と[Amplify(エンプリファイ)](https://aws.amazon.com/jp/amplify/)のAuth SDKを使って~~手抜きで~~簡単にログイン認証基盤を作ろうと思い立ったのですが、一番ラクそうだった[Cognitoが予め用意している認証UIは日本語対応不可](https://www.slideshare.net/slideshow/embed_code/key/2cEJN3Tcx0GDI4?startSlide=41)だったためオリジナル管理画面を作るしかない...ということに。

いざオリジナルのユーザ管理画面を作成するぞ...と思い立ったら結構めんどくさくて忘れそうだったので環境構築から実装までの手順をまとめてみました。



## システム構成

今回構築するシステム構成図をまとめてみました。

![【AWS】Cognito + AmplifyでオリジナルのWebユーザ管理画面を作成する手順.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/e1029b07-6c6f-b002-f7f6-1729c8761ed1.png)

ローカル環境上のWebページ(HTML)からAmplify Auth SDK(JavaScript)でサインアップ、サインイン、サインアウトAPIを呼び出すことでネットワーク越しのCognitoユーザプールにイロイロする...という構成です。

## 本記事の対象者

Cognitoのユーザプール、IDプールなどの概念はなんとなくわかったけれど、Webページからのサインアップ、サインイン、サインアウトの処理の書き方はイマイチピンと来ていない方
※ まだCognitoの概念についてよくわからない方は[Blackbelt](https://www.slideshare.net/AmazonWebServicesJapan/20200630-aws-black-belt-online-seminar-amazon-cognito)を一読ください


## アジェンダ

- 今回作成するリソースの構成
- Cognitoユーザプールの作成
- 実装に必要なパラメータを控える
- 認証処理の実装

# 実際にやってみる

それでは実際に作っていきます。

## Cognitoユーザプールの作成

サインアップしたユーザ情報を管理するCognitoユーザプールを先に作成していきます。

AWSマネジメントコンソールの[Cognitoのユーザプール管理画面にアクセス](https://ap-northeast-1.console.aws.amazon.com/cognito/users/?region=ap-northeast-1)します。
**「ユーザプールを作成する」ボタンを押下します。**
![スクリーンショット 2020-08-31 13.01.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/42029809-09e4-5458-0021-8323f1ad1317.png)
<font color="gray"/>※ 既存のユーザプールも存在しますが、スルーしてください...</font>

**作成したユーザプールを管理するためのプール名を入力して、「ステップに従って設定する」ボタンを押下します。**
![スクリーンショット 2020-08-31 13.01.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/46e7c80d-14a7-eddb-a3f1-b3ca0ea96a4c.png)

**今回はユーザのサインイン情報としてEメールアドレスを利用します。**
<font color="red">※ 本情報はユーザプール作成完了後に変更することはできないため注意してください。</font>
![スクリーンショット 2020-08-31 13.02.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/6735087b-b65b-a7df-36ac-2f9ba96964b3.png)

サインアップ時に必要な属性情報を定義します。
今回は追加しません。
<font color="red">※ 本情報はユーザプール作成完了後に変更することはできないため注意してください。
　ただし**カスタム属性は後から変更することが可能**です。
</font>
![スクリーンショット 2020-08-31 13.03.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/7cd7d5bb-8182-304f-075c-bfa4bf809334.png)

**サインアップ時のパスワードルールなどを定義します。**
基本的には好みの設定情報で大丈夫ですが、**「ユーザに自己サインアップを許可しますか？」**の項目は許可するをチェックしておかないと、管理者以外の不特定多数がサインアップすることができなくなるため注意してください。
![スクリーンショット 2020-08-31 13.03.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/8a42cd67-8747-1f23-64f7-3bb99dfecc64.png)

二段階認証(MFA)は今回はOFFにします。
ユーザがパスワードを忘れた際のリマインドは特別な要件がない限り、初期値でOKです。
![スクリーンショット 2020-08-31 13.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/97ea4abb-bdaa-7f4c-9fd0-7652d5c09d30.png)

**サインアップ情報の確認としてEメールを選択しておきます。**
またサインアップ情報の確認として電話番号を選択した際には、SMS送信用のIAMロールが必要ですが、今回はEメールですのでここは何も設定せずにスキップします。
![スクリーンショット 2020-08-31 13.04.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/0636aaef-4cfc-b8de-eb1b-946b2d60f4af.png)

**「Eメールアドレスをカスタマイズしますか？」**ではサインアップ時やリマインド時にCognitoから送信されるEメールの情報を編集できますが、ここではデフォルトで進めます。
※ 独自ドメインを利用した場合などはここで設定します

「Amazon SES設定を通じてEメールを送信しますか？」ではデフォルトの[Cognitoからのメール送信では一日50件などの制限](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/limits.html#limits-hard)があるため、その制限を撤廃する際にAmazon SESの設定を利用する必要があります。
今回はあくまで検証であるため、デフォルトのCognitoからのメール送信設定を利用します。
![スクリーンショット 2020-08-31 13.05.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/4a82c43a-6229-b576-2ac3-37a94d0a9709.png)

**「Eメール検証メッセージをカスタマイズしますか？」**では、サインアップ時のEメールアドレス検証の際に送信されるメール文の内容を編集できます。
ここではデフォルトで進行します。

**「ユーザ招待メッセージをカスタマイズしますか？」**では、管理者が作成したユーザを利用して招待を行う時に送信されるメール文の内容を編集できます。
ここではデフォルトで進行します。
![スクリーンショット 2020-08-31 13.05.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/0177e6ac-595d-2784-3a0f-0c113f080235.png)

タグの追加はご自由に...。
![スクリーンショット 2020-08-31 13.06.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/d14cb5d7-d10c-b990-ce63-985fb4a94b67.png)

**「ユーザのデバイスを記録しますか？」**では、ログイン時にユーザが利用したデバイス情報を保存して、デバイス毎に認証処理を変更したい時などに設定を変更します。
今回はデフォルトのデバイス記録はしない「いいえ」で進行します。
![スクリーンショット 2020-09-05 18.44.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/2ddf92d6-9152-e863-553e-28d04fb05939.png)

**「アプリクライアントの追加」**をクリックして、今回作成するCognitoユーザプールを利用するアプリケーションの情報を登録する。
とりあえず画像と同じように設定してもらえればOKだが、各項目の詳細は以下の通り。

| 項目 | 詳細 |
|:--|:--|
| アプリクライアント名 | アプリケーションの名前 |
| トークンの有効期限を更新  | Idトークン、アクセストークンの有効期限が切れた際にトークンの更新を行うリフレッシュトークンの有効期限。<br>リフレッシュトークンの有効期限が切れたら再度ログインが必要になる。|
| アクセストークンの有効期限 | Cognitoユーザプールに登録したユーザ情報を更新する際に利用するアクセストークンの有効期限を設定する。 |
| Idトークンの有効期限 | Cognitoユーザプールに登録したユーザ情報を参照する際に利用するIdトークンの有効期限を設定する。|
| クライアントシークレットを作成 | OAuth2.0のクライアントシークレット認証を行う際に必要なクライアントシークレットを生成する。<br>クライアントシークレットをセキュアに保持できるサーバアプリからの認証出ない限り、チェックを外す。 |
| 認証フローの設定 | [こちら](https://stackoverflow.com/questions/54238306/what-the-settings-mean-in-aws-cognito-user-pool-app-client/54255143)が詳しい |
![スクリーンショット 2020-09-05 18.52.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/c0f50f40-84dc-4c1b-d2ea-b77dcb82292d.png)

Cognitoユーザプールへの各アクション(サインアップ、認証...etc)の前後にLambdaを挟んで独自の処理を実行することが可能です。
今回は設定なしで進行します。

![スクリーンショット 2020-09-05 20.06.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/12479896-ac2d-7056-f218-0a73e498d68b.png)

最後に入力内容を確認した後**「プールの作成」**を押下してユーザプールの作成が行われます。

![スクリーンショット 2020-08-31 14.25.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/d0b6ea5e-a31d-52e7-fd93-eb2d94c4466b.png)

## 実装に必要なパラメータを控える
Cognitoユーザプールの作成は完了したのですが、実装に移る前に実装に必要なパラメータをいくつか控えます。

### CognitoユーザプールID

先程登録したCognitoユーザプールにアクセスします。
![スクリーンショット 2020-09-05 20.14.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/47dd5386-ccd6-95ba-5dda-52c2fd9f29df.png)

その後、**CognitoユーザプールIDが表示されるため値を控えます。**
![スクリーンショット 2020-09-05 20.14.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/31c12133-82ad-97a9-a9f3-f6e56777e75e.png)

### CognitoアプリクライアントID
先程登録したCognitoユーザプールにアクセスします。
![スクリーンショット 2020-09-05 20.14.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/47dd5386-ccd6-95ba-5dda-52c2fd9f29df.png)

その後、**アプリクライアントIDを控えます。**
![スクリーンショット 2020-09-05 20.14.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/6352d8e5-8393-79db-c379-8b077908b8c2.png)


## 認証処理の実装

ようやくAWS Amplifyを利用してCognitoを利用した認証画面を実装します。
後述するサインアップなどの処理を組み込むことでどんなフレームワークを利用しても問題なくオリジナルのユーザ管理画面が作成できます。
※ サンプルではReactを利用しています

### aws-amplifyのインストール

```bash:bash
npm install aws-amplify
```
npmでもyarnでもお好きな方で。

### AmplifyのAuth SDK用の設定作成

```javascript:javascript
import Amplify, { Auth } from 'aws-amplify';

// Amplifyの設定
Amplify.configure({
  Auth: {
    // リージョン
    region: '{ユーザプールを生成したリージョン}',
    // ユーザプールのID
    userPoolId: '{上記手順で控えたユーザプールID}',
    // アプリクライアントID
    userPoolWebClientId: '{上記手順で控えたアプリクライアントID}',
  }
});
```

実際の値は自身の環境の値で置き換えてください。

### サインアップ処理の実装

```javascript:javascript
async function signUp (userName, password) {
  try {
    await Auth.signUp({
      username: userName, 
      password: password
    });
  } catch (error) {
    console.log(error);
  }
};
```

![signUp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/09ec7426-d634-7e47-f9c7-0788234bc9f2.gif)


先程作成したCognitoユーザプールに新しくユーザをサインアップします。
実際にAWSマネジメントコンソール上からCognitoユーザプールにアクセスすると...。

![スクリーンショット 2020-09-05 21.14.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/640841ab-20f3-a385-f184-e0a16b52b187.png)

ユーザが追加されていますね。
しかし`UNCONFIRMED`との通り、確認待ちの状態です。

サインアップ時に入力したメールアドレスに確認のメールが届いています。

![スクリーンショット 2020-09-05 21.31.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/f3a18eb8-63fa-27d7-6e17-e461eea8b466.png)


届いた認証コードを利用して認証APIを呼び出すことで認証が完了してサインアップが完了します。
※ ちなみに`await Auth.resendSignUp(userName);`で、認証コードの再発行が可能です。

この処理を自作したサインインボタンのクリックイベントに組み込むことでCognitoユーザプールにサインアップが行えます。

### 認証コード検証処理の実装

```javascript:javascript
async function confirmSignUp (userName, code) {
  try {
    await Auth.confirmSignUp(
      username: userName,
      code: code
    );
  } catch (error) {
    console.log(error);
  }
}
```

![code.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/153f22bc-8caf-e376-2f2f-a5115e8b16be.gif)

サインアップ時に届いた認証コードを検証する処理です。
認証コードが正しければ、Cognitoユーザプールに追加されたユーザのステータスが`UNCONFIRMED`から`CONFIRMED`に変更されます。

![スクリーンショット 2020-09-05 21.17.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/aec57d3b-b96c-aa1a-4605-11aed2d3b7f4.png)

この処理を用意した認証コード検証ボタン(仮)のクリックイベントに組み込むことで認証コードの検証が行えます。


### サインイン処理の実装

```javascript:javascript
async function signIn (userName, password) {
  try {
    await Auth.signIn({
      username: userName, 
      password: password
    });
  } catch (error) {
    console.log(error);
  }
};
```

![signIn.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/d6a10e90-9284-113f-564e-89545eb1c22c.gif)


Cognitoユーザプールに存在するユーザ情報を利用してサインインします。
サインイン成功後はAmplifyが自動でローカルストレージにIdトークン、アクセストークン、リフレッシュトークンなどを格納します。
現在サインインしているユーザの情報などを取得する時は、`await Auth.currentAuthenticatedUser();`などを利用することで取得することが可能です。
※ 他にも[様々な情報をAuthクラスから取得](https://aws-amplify.github.io/amplify-js/api/classes/authclass.html)できます。


また、Idトークン・アクセストークンの有効期限が切れた際のリフレッシュトークンを利用したトークンの更新処理も自動で裏側でやってくれています。
※ Amplifyが裏側で行ってくれている処理の詳細を知りたい場合は、[クラスメソッド先生の記事](https://dev.classmethod.jp/articles/learn-authentication-using-cognitos-hosted-ui-with-amplify/#toc-11)が参考になります。

サインインボタンのクリックイベントに本処理を組み込むことでサインインが行えるようになります。

### サインアウト処理の実装

```javascript:javascript
async function signOut() {
  try {
    await Auth.signOut();
  } catch (error) {
    console.log(error);
  }
}
```

![signOut.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312085/a2e1164d-b3a4-a773-58ac-29683ac36748.gif)

ローカルストレージに保存されているIdトークン、アクセストークン、リフレッシュトークンなどの情報をクリアしてサインアウトを実施します。

用意したサインアウトボタンのクリックイベントに本処理を組み込むことでサインアウトが行えるようになります。

### 最終的な成果物

```javascript:javascript
import Amplify, { Auth } from 'aws-amplify';

// Amplifyの設定
Amplify.configure({
  Auth: {
    // リージョン
    region: '{ユーザプールを生成したリージョン}',
    // ユーザプールのID
    userPoolId: '{上記手順で控えたユーザプールID}',
    // アプリクライアントID
    userPoolWebClientId: '{上記手順で控えたアプリクライアントID}',
  }
});

//　サインアップ
async function signUp (userName, password) {
  try {
    await Auth.signUp({
      username: userName, 
      password: password
    });
  } catch (error) {
    console.log(error);
  }
};

// 認証コード検証
async function confirmSignUp (userName, code) {
  try {
    await Auth.confirmSignUp(
      username: userName,
      code: code
    );
  } catch (error) {
    console.log(error);
  }
}

// サインイン
async function signIn (userName, password) {
  try {
    await Auth.signIn({
      username: userName, 
      password: password
    });
  } catch (error) {
    console.log(error);
  }
};

// サインアウト
async function signOut() {
  try {
    await Auth.signOut();
  } catch (error) {
    console.log(error);
  }
}
```

実装した処理を作成したボタンなどのイベントと紐付けることで、オリジナルのWebユーザ管理画面が作成できます。

# まとめ
こんな感じでAWS Cognito + AWS Amplifyでオリジナルのサインアップ・サインイン・サインアウト画面を実装することが可能です。
Cognitoは便利なサービスだと思うのですが、日本語の記事が少なく始めるハードルが結構高い気がしたので、どなたかの助けになると幸いです。

記事内に誤りなどありましたら、ご指摘いただけると幸いです。