【React】ユーザー認証をCognito + Amplifyで構築してみた ~ 構築準備編 ~

## はじめに
Reactで作成したWebアプリケーションのユーザー認証部分をCognito + Amplifyフレームワークで構築してみました。
本記事では、それぞれのサービスについて、Amplifyの設定方法についてまとめています。

### 完成画面
今回は、ユーザー名、パスワードとワンタイムパスワードの２段階認証を行います。そして、サインインをしたユーザーを識別し、トップページには「{ユーザー名}さんこんにちは!」という文字が出るようにします。

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/8e01dda5-89f5-f5da-e792-038725e65f6f.gif)

## 方法検討
### 要件
認証方法を考えるにあたり、条件は以下の通りです。

+ 静的コンテンツをS3に置いている
+ アプリケーション部分はLambdaで実装している
+ アプリケーション内でユーザーを管理しており、ユーザーに登録されている人だけ使えるようにしたい
+ 今後ユーザーのマイページも作りたいので、認証と合わせてユーザーを識別したい
+ パスワードはシステム管理者が管理しないようにするのがベター
+ ソーシャルアカウントは使わない
+ MFA(多要素認証)を使いたい

### 結論
この条件に沿って認証部分を考えた結果、Cognitoを利用することにしました。また、Cognitoと既存のWebアプリケーションを簡単に連携できるAWSのAmplifyフレームワークというものがあったので、こちらも使ってみることにしました。

## 使用するAWSサービスについて
### Cognitoってなに？

[公式サイト](https://aws.amazon.com/jp/cognito/)によると、次のように説明されています。

> Amazon Cognito を使用すれば、ウェブアプリケーションおよびモバイルアプリに素早く簡単にユーザーのサインアップ/サインインおよびアクセスコントロールの機能を追加できます。Amazon Cognito は、数百万人のユーザーにスケールし、Facebook、Google、Amazon などのソーシャル ID プロバイダー、および SAML 2.0 によるエンタープライズ ID プロバイダーを使用したサインインをサポートします。

#### Cognitoでできること
今回のCognitoでやることは下記の通りです。

+ Webアプリケーションのユーザーの管理（パスワードなど含む）
+ ユーザーがサインインするときの認証

#### 料金
料金は使った分だけです。最低料金もかかりません。
今回のように、ユーザープールの認証情報を使ってCognitoユーザープールに直接サインインする場合は50,000MAUの無料枠があります。50,000MAUを超えるアクティブユーザーがいる場合は、0.0055USD/MAUかかります。（人数幅によって多少減額あり。詳細は[こちら](https://aws.amazon.com/jp/cognito/pricing/)をご覧ください。）
※1MAU=サインアップ、サインイン、トークンの更新またはパスワードの変更など、当該ユーザーに関わる ID 操作があったユーザー（アクティブユーザー）

参考：https://aws.amazon.com/jp/cognito/pricing/

### Amplifyってなに？

[公式サイト](https://aws.amazon.com/jp/amplify/)によると、次のように説明されています。

> AWS Amplify は、モバイルとウェブのフロントエンドデベロッパーが、安全でスケーラブルなフルスタックアプリケーションを構築しデプロイできるようにする、AWS によるエンドツーエンドのソリューションです。Amplify を使用すれば、アプリケーションのバックエンドを数分で設定し、わずか数行のコードでそれをアプリケーションに接続できます。そして、3 ステップで静的なウェブアプリケーションをデプロイできます。

その中で、分析や認証などの機能を持つAWSリソースを構成するのに役立つ対話型のCLIを提供していたり、認証処理などの実装や画面の作成に使うライブラリ、UIコンポーネントを提供している**Amplifyフレームワーク**というものを使います。

#### Amplifyフレームワークでできること

今回Amplifyフレームワークでやることは下記の通りです。

+ Amplify CLIを使ってのCognitoユーザープールの構築
+ 既存のソースコードとCognitoの連携
+ サインイン画面作成
+ ユーザー情報の取得、サインアウト機能の実装

今回はフロントエンドフレームワークとしてReactを使いましたが、他にもReact Native、Angular、Vue、IonicやiOS/Android IDEでも使うことができます。

#### 料金

Amplifyフレームワーク自体には料金はかかりません。

> Amplify フレームワーク (ライブラリ、CLI、UIコンポーネント) を使用する場合は、基盤として使用する AWS のサービスに対してのみお支払いいただきます。Amplify フレームワークの使用には、追加料金は発生しません。

参考：https://aws.amazon.com/jp/amplify/pricing/?nc=sn&loc=3


## 手順
下記の流れで進めていきます。

本記事ではこちらの手順についてまとめています。
1. Reactの開発環境の構築
2. Amplifyの設定
3. Amplify用のIAMユーザーの作成
4. Amplifyの初期設定

下記の5番、6番については別記事 [構築完成編](https://qiita.com/maika_kamada/items/bd55a9fa3dd551d63b94) へ。
5. Cognitoユーザープールの作成
6. コード実装


## やってみる
### 1. Reactの開発環境の構築

React環境を簡単に構築する環境構築ツールは`create-react-app`、パッケージマネージャーは`yarn`を使いました。

参考：[新しい React アプリを作る](https://ja.reactjs.org/docs/create-a-new-react-app.html)

#### バージョン

+ node 14.15.0
+ yarn 1.22.10
+ create-react-app 4.0.0


### 2. Amplifyの設定

AmplifyをCLIで操作するために、Amplify CLIをインストールします。

```
$ yarn global add @aws-amplify/cli
```

そして、プロジェクトには、Amplifyのパッケージを追加します。

```
$ yarn add aws-amplify
$ yarn add aws-amplify-react
$ yarn add @aws-amplify/ui-react
```


### 3. Amplify用のIAMユーザーの作成

AWSでAmplifyが使うIAMユーザーの作成し、名前付きプロファイルにIAMユーザーを設定します。

```
$ amplify configure
```

上記のコマンドを実行すると、AWSのコンソールが自動で開くのでコンソールでIAMユーザーを作成します。その後、コマンドラインで対話形式で作成したIAMユーザーの認証情報をAWS CLIのcredentialsファイルに保存します。今回、プロファイル名は`amplify-cognito`で保存しました。

詳しくは、こちらの記事の**AWSアカウントの紐付け**を参考にしました。
[AWS Amplify CLIの使い方〜インストールから初期セットアップまで〜](https://qiita.com/Junpei_Takagi/items/f2bc567761880471fd54)

ただ、Amplify用のIAMユーザーに**AdministratorAccess**のポリシーをアタッチするのは嫌だったので、[こちら](https://docs.amplify.aws/cli/usage/iam)を参考に自分でポリシーを作成しました。不要そうなサービスへの権限を削除し、これでは権限が足りずエラーが出てしまったので、`"cognito-idp:UntagResource"`を追加しています。

※↓クリックするとIAMポリシーが見れます。

<details><summary>IAMポリシー</summary><div>

```json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "amplify:CreateApp",
                "amplify:CreateBackendEnvironment",
                "amplify:CreateBranch",
                "amplify:DeleteApp",
                "amplify:DeleteBackendEnvironment",
                "amplify:DeleteBranch",
                "amplify:GetApp",
                "amplify:GetBackendEnvironment",
                "amplify:ListApps",
                "amplify:ListBackendEnvironments",
                "amplify:ListBranches",
                "amplify:ListDomainAssociations",
                "amplify:UpdateApp",
                "cloudformation:CreateChangeSet",
                "cloudformation:CreateStack",
                "cloudformation:CreateStackSet",
                "cloudformation:DeleteStack",
                "cloudformation:DeleteStackSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:DescribeStackEvents",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStackResources",
                "cloudformation:DescribeStackSet",
                "cloudformation:DescribeStackSetOperation",
                "cloudformation:DescribeStacks",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:GetTemplate",
                "cloudformation:UpdateStack",
                "cloudformation:UpdateStackSet",
                "cognito-identity:CreateIdentityPool",
                "cognito-identity:DeleteIdentityPool",
                "cognito-identity:DescribeIdentity",
                "cognito-identity:DescribeIdentityPool",
                "cognito-identity:SetIdentityPoolRoles",
                "cognito-identity:GetIdentityPoolRoles",
                "cognito-identity:TagResource",
                "cognito-identity:UpdateIdentityPool",
                "cognito-idp:AdminAddUserToGroup",
                "cognito-idp:AdminCreateUser",
                "cognito-idp:CreateGroup",
                "cognito-idp:CreateUserPool",
                "cognito-idp:CreateUserPoolClient",
                "cognito-idp:DeleteGroup",
                "cognito-idp:DeleteUser",
                "cognito-idp:DeleteUserPool",
                "cognito-idp:DeleteUserPoolClient",
                "cognito-idp:DescribeUserPool",
                "cognito-idp:DescribeUserPoolClient",
                "cognito-idp:ListTagsForResource",
                "cognito-idp:ListUserPoolClients",
                "cognito-idp:UpdateUserPool",
                "cognito-idp:UpdateUserPoolClient",
                "cognito-idp:UntagResource",
                "iam:AttachRolePolicy",
                "iam:CreatePolicy",
                "iam:CreateRole",
                "iam:DeletePolicy",
                "iam:DeleteRole",
                "iam:DeleteRolePolicy",
                "iam:DetachRolePolicy",
                "iam:GetPolicy",
                "iam:GetRole",
                "iam:GetRolePolicy",
                "iam:GetUser",
                "iam:ListPolicyVersions",
                "iam:PassRole",
                "iam:PutRolePolicy",
                "iam:UpdateRole",
                "lambda:AddLayerVersionPermission",
                "lambda:AddPermission",
                "lambda:CreateEventSourceMapping",
                "lambda:CreateFunction",
                "lambda:DeleteEventSourceMapping",
                "lambda:DeleteFunction",
                "lambda:DeleteLayerVersion",
                "lambda:GetEventSourceMapping",
                "lambda:GetFunction",
                "lambda:GetFunctionConfiguration",
                "lambda:GetLayerVersion",
                "lambda:GetLayerVersionByArn",
                "lambda:InvokeAsync",
                "lambda:InvokeFunction",
                "lambda:ListEventSourceMappings",
                "lambda:ListLayerVersions",
                "lambda:PublishLayerVersion",
                "lambda:RemoveLayerVersionPermission",
                "lambda:RemovePermission",
                "lambda:UpdateFunctionCode",
                "lambda:UpdateFunctionConfiguration",
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:DeleteBucketPolicy",
                "s3:DeleteBucketWebsite",
                "s3:DeleteObject",
                "s3:GetBucketLocation",
                "s3:GetObject",
                "s3:HeadBucket",
                "s3:ListAllMyBuckets",
                "s3:ListBucket",
                "s3:PutBucketAcl",
                "s3:PutBucketCORS",
                "s3:PutBucketNotification",
                "s3:PutBucketPolicy",
                "s3:PutBucketWebsite",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "*"
        }
    ]
}
```

</div></details>


これより少なくすることはできるかもしれないですが、この権限があれば今回のCognitoとAmplifyでユーザー認証を構築するのは問題なくできました！

### 4. Amplifyの初期設定

Amplifyの設定を行うために下記のコマンドを実行します。

```
$ amplify init
```

聞かれた通りにプロジェクト名や使っているエディターなどを入力、選択していきます。プロジェクト名や環境名は、AWS上に作成されるリソースの名前になります。

```
? Enter a name for the project qiitatest
? Enter a name for the environment dev
? Choose your default editor: IntelliJ IDEA
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using react
? Source Directory Path:  src
? Distribution Directory Path: build
? Build Command:  yarn build
? Start Command: yarn start
```

そして、使うプロファイルについて聞かれるので`y`と答え、先ほど[3. Amplify用のIAMユーザーの作成](#3-amplify用のiamユーザーの作成)で作成、保存したプロファイル`amplify-cognito`を選択します。

```
? Do you want to use an AWS profile? Yes
? Please choose the profile you want to use amplify-cognito
```

この設定が完了すると、プロジェクト内に`amplifyフォルダ`と`src/aws-exports.js`が作成されます。

## おわりに
Amplifyフレームワークの設定が完了しました！Amplify用のIAMユーザーの作成方法（ポリシーの内容は変わってきます）やAmplifyの初期設定についてはCognitoに限らず他のサービスでも共通です。
次の[構築完成編](https://qiita.com/maika_kamada/items/bd55a9fa3dd551d63b94)では、Cognitoユーザープールの作成とソースコードも書いていきます！