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

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


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

![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フレームワークというものがあったので、こちらも使ってみることにしました。

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

下記の1番から4番については別記事 [構築準備編](https://qiita.com/maika_kamada/items/1289b8926208d35751b1) へ。
1. Reactの開発環境の構築
2. Amplifyの設定
3. Amplify用のIAMユーザーの作成
4. Amplifyの初期設定

本記事ではこちらの手順についてまとめています。
5. Cognitoユーザープールの作成
6. コード実装


## やってみる
### 5. Cognitoユーザープールの作成
前述の要件に沿ってユーザープールを作成していきます。
初めてのCognitoで何の設定をしているのかを調べるのが大変だったので1つ1つ残しておこうと思います。

Cognitoの作成を行うために下記のコマンドを実行します。

```
$ amplify add auth
``` 

詳細な設定を行うか、デフォルトの設定で良いか聞かれます。今回はMFAを使いたかったので`Manual configuration`を選択しました。

```shell

# Do you want to use the default authentication and security configuration?
Manual configuration
```

今回はサインインが使えれば良かったので、`User Sign-Up & Sign-In only (Best used with a cloud API only)`を選択しました。

```shell

# Select the authentication/authorization services that you want to use:
User Sign-Up & Sign-In only (Best used with a cloud API only)
```

リソース名とユーザープール名を入力します。

```shell

# Please provide a friendly name for your resource that will be used to label this category in the project: 
test
# Please provide a name for your user pool: 
test-cognito-mfa
```

ユーザー名とパスワードでサインインしたいので、`Username`を選択します。この項目は、後から変更できません。

```shell

# How do you want users to be able to sign in?
Username
```

**参考**
![スクリーンショット 2020-11-01 14.47.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/505ba7b1-0a3e-214f-8277-a8633706864b.png)


今回はグループは作成しないので`No`を選択します。

```shell

# Do you want to add User Pool Groups? 
No
```

ユーザープール内の情報を取得したり、ユーザーを登録したりするAPIを叩けるようにするかを選択します。今回は使わないので`No`を選択します。
参考：https://docs.amplify.aws/cli/auth/admin

```shell

# Do you want to add an admin queries API?
No
```

今回はアプリケーション内でユーザーを管理していたため、このAPIを使わずに、自分でLambdaを作成しAWSのSDKを使ってユーザーの登録などを実装しましたが、このAPI使うのでも良かったかもしれないです。

MFAの設定を行います。今回はMFAを使いたいので`ON `を選択します。この設定はあとで変えられないので気をつけましょう。そして、MFAにはAuthenticatorやAuthyなどのアプリケーションを用いてのワンタイムパスワードを使いたいので、`TOTP`のみを選択しました。

```shell

# Multifactor authentication (MFA) user login options:
 ON (Required for all logins, can not be enabled later)
# For user login, select the MFA types: 
 Time-Based One-Time Password (TOTP)
```

**参考**
![スクリーンショット 2020-11-03 19.28.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/b3cdb8cc-57f5-10db-b591-da0ccd5fcb7e.png)


SMSのメッセージの設定です。
SMSは使わず、Eメールを使うつもりなので、デフォルトのままEnterします。

```shell

# Please specify an SMS authentication message: 
 Your authentication code is {####}
```


ユーザープールにはEメールアドレスも登録し、Eメールを使ってユーザーの登録やパスワードの再設定を行いたいので`Enabled`を選択します。

```shell

# Email based user registration/forgot password:
Enabled (Requires per-user email entry at registration)
```

**参考（左メニュー [MFAそして確認] 内）**
![スクリーンショット 2020-11-03 23.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/2768e2e4-c084-68fa-8ee4-3b695f0a36d2.png)


認証コードを送るEメールの題名と本文の設定です。デフォルトのまま進みます。

```shell

# Please specify an email verification subject:
Your verification code
# Please specify an email verification message:
Your verification code is {####}
```

**参考（左メニュー [メッセージのカスタマイズ] 内）**
![スクリーンショット 2020-11-06 23.10.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/d8dd84e7-e440-92c7-9698-435c2cccf034.png)


パスワードポリシーを設定します。
文字数を８文字以上、数字、特殊文字、大文字、小文字を含むパスワードのみしか設定できないようにしました。

```shell

# Do you want to override the default password policy for this User Pool?
Yes
# Enter the minimum password length for this User Pool:
8
# Select the password character requirements for your userpool:
Requires Lowercase, Requires Uppercase, Requires Numbers, Requires Symbols
```
**参考**
![スクリーンショット 2020-11-01 14.14.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/c22c056a-800f-77d8-46a6-481ab6cfc60f.png)

ちなみに、`Do you want to override the default password policy for this User Pool?`を`No` (= デフォルトのパスワード設定)とすると、８文字以上という条件しか設定されません。

サインアップ時に必要な属性を設定します。パスワードの再設定等にメールアドレスを使用するため、`Email`を選択しました。この項目は、後から変更できません。

```shell

# What attributes are required for signing up?
Email
```

**参考(左メニュー [属性] 内)**
![スクリーンショット 2020-11-01 14.50.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/0695e4df-e18d-1c12-0133-349ac8e75981.png)

リフレッシュトークンはひとまずデフォルトの30日にしておきます。

```shell

# Specify the app's refresh token expiration period (in days): 
30
```

**参考**
![スクリーンショット 2020-11-01 14.35.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/b16612c3-c59f-d65a-87b8-30f31fd2da1d.png)


サインアップはユーザー自身が行わず別の処理で行うので書き込み権限は付与しません。また、ログインしたユーザーの識別にはUsernameを使うので読み取り権限も付与しません。

```shell

# Do you want to specify the user attributes this app can read and write?
No
```

今回は、アプリケーション内で事前に登録しているユーザーのみしかログインできず、ユーザー自身がサインアップすることがないので追加しませんでしたが、下記のような機能を追加することもできます。

+ Add Google reCaptcha Challenge
+ Email Verification Link with Redirect
+ Add User to Group
+ Email Domain Filtering (blacklist)
+ Email Domain Filtering (whitelist)
+ Custom Auth Challenge Flow (basic scaffolding - not for production)

```shell

# Do you want to enable any of the following capabilities? 
（選択なし）
```

今回はソーシャルログインは使わないのでOAuthは`No`を選択します。

```shell

# Do you want to use an OAuth flow?
No
```

CognitoをLambda関数のトリガーとして使う予定はなかったので`n`を入力。
例えば、ユーザーがサインアップしたことをトリガーとしてLambda関数を実行したいときなどに利用できます。

```shell

# Do you want to configure Lambda Triggers for Cognito?
No
```

**参考**
![スクリーンショット 2020-11-01 14.22.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/d03457fb-c09a-257b-11c9-0888d85f8fe1.png)


これで構築するCognitoの設定が完了です。
そして、下記のコマンドで設定を保存します。

```
$ amplify push
```

環境やリソースがあっているかを聞かれるので、確認して`y`を入力します。

```shell

# ? Are you sure you want to continue?
Yes
```

これでCognitoのユーザープールが作成されました！そして`src/aws-exports.js`にはid等が書き込まれます。

最後に、このままだとユーザー自身がサインアップできてしまうので、Cognitoコンソールからできないようにします。
先ほど作成したCognitoのユーザープールを開き、左のメニューから[ポリシー]をクリックし、[管理者のみにユーザーの作成を許可する]を選択します。その後、[変更の保存]をクリックし、保存してください。

![スクリーンショット 2020-11-03 23.27.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/e4c81b78-3804-c31e-c094-85bcb1d6f2f9.png)



Cognitoのコンソールからも設定を変更することもできますが、一部コンソールの変更では反映されないというのも見かけましたので、ご注意ください。Amplify CLIで設定を変更する場合は、`$ amplify update auth`で設定を変更し、`$ amplify push`を実行することで変更が保存されます。

### 6. コード実装

`App.js`の中身を書き換えます。
参考：https://docs.amplify.aws/lib/auth/getting-started/q/platform/js

```App.js

import React, {useEffect} from "react";
import Amplify, {Auth} from 'aws-amplify';
import awsconfig from './aws-exports';
import {withAuthenticator} from "@aws-amplify/ui-react";

Amplify.configure(awsconfig);

function App() {
    const [currentUserName, setCurrentUserName] = React.useState("");
    useEffect(() => {
        const init = async() => {
            const currentUser = await Auth.currentAuthenticatedUser();
            setCurrentUserName(currentUser.username);
        }
        init()
    }, []);

    const signOut = async() => {
        try {
            await Auth.signOut();
        } catch (error) {
            console.log('error signing out: ', error);
        }
        document.location.reload();
    }

    return (
        <div>
            <h1>{currentUserName}さんこんにちは！</h1>
            <button onClick={signOut}>サインアウト</button>
        </div>
    );
}

export default withAuthenticator(App);

```


## 実行結果
### ユーザーの追加
ユーザーの追加を行います。まだ、アプリケーション内から登録ができるようになっていないので、今回はAWSのCognitoコンソールから追加を行います。

先ほど作成したユーザープール内の左メニュー[ユーザーとグループ]を選択し、[ユーザーの作成]をクリックします。

![スクリーンショット 2020-11-07 16.37.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/91328cf8-4c71-5dc4-3968-fe2b3329497f.png)

ユーザー名、ユーザーへの招待はEメールで行いたいのでEメールにチェック、電話番号は登録しないので検証済みのチェックを外し、Eメールアドレスを入力します。そして、[ユーザーの作成]をクリックし、ユーザーを作成します。ここで仮パスワードを未入力で登録すると、ユーザーにはランダムでパスワードが発行されます。

![スクリーンショット 2020-11-07 16.43.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/5fa535e8-d10f-51aa-275e-0fe0e7a659a8.png)

ユーザーが作成できました。

![スクリーンショット 2020-11-07 16.43.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/9bda1850-d9c7-7d38-1c9c-b4dad69e5403.png)

これでユーザーの登録は完了です。そしてユーザーには次のようなメールが送られます。

![スクリーンショット 2020-11-07 16.44.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/d36d95b2-0636-b077-8393-4ef3c216175b.png)

### サインイン一連の流れ（2回目以降）
冒頭の[完成画面](#完成画面)をご覧ください。
※初回サインイン時には、パスワードの変更、TOTP用のQRコードを読み取る操作が加わります。

### サインインページからアカウントは本当につくれないのか
最後にAWSコンソールからユーザーがサインアップできないように設定しましたが、本当にできないのかを試してみます。

![スクリーンショット 2020-11-07 18.01.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546572/bea39164-5138-a90c-c59b-dfee674a723b.png)


エラーが出てサインイン画面からは登録できないことが確認できました！

## おわりに

こんなに簡単にユーザー認証をつけることができました！画面もAmplifyのUIコンポーネントを使えば、こんなにも簡単に作成することができるんです！
ただ、このままではサインインページに使えないアカウント作成ボタンがあるので、そのボタンをなくしたい、AWSコンソールからではなくアプリケーションでユーザー管理できるようにしたいなど、要件に合うように多少カスタマイズも必要です。そのカスタマイズについて今後記事に残していければと思います。