Cognito + React でメアド検証を行い、ユーザーを登録する

# はじめに
cognitoを使ってユーザー管理をしたい！と思ったのでreactと組み合わせて実装してみます。

## Cognitoユーザープールを作成する

Cognitoユーザープールを作成します。

![cap01.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/3b445401-4e2d-2be4-6c2e-470cd7138c6e.png)

参考：

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools.html

### ユーザープール作成 ステップ1
サインインオプションを指定します。
今回はEメールアドレスとパスワードで認証したいので、
ここではEメールを指定しておきます。

![cap02.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/4f84bf39-8db7-8e73-e530-61691f35439c.png)

次へをクリックし、進みます。

### ユーザープール作成 ステップ2
セキュリティ要件を設定します。

パスワードポリシーはデフォルトのままにします。

![cap03.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/8b7d90d5-fce3-c855-5fad-fd1a34ab8cea.png)

テストなので、MFAはなしにしておきます。

![cap04.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/0c31f767-6b94-2b7d-78da-87c97cf3d9cc.png)

次へをクリックし、進みます。

### ユーザープール作成 ステップ3

サインアップ処理の挙動を設定します。
フロントエンドから認可なしでサインアップ関数を実行するため、自己登録を有効化しておきます。

![cap05.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/bb1860c3-1472-40f8-9f90-740e216af0ce.png)

サインアップ時に検証メッセージを自動送信する設定を有効化しておきます。
無効にした場合、lambda、SESなどで自前実装できます。

![cap06.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/2f4ca660-9d78-edc9-49be-5f6998ac5fc7.png)

その他の設定はデフォルトのままにしておきます。

![cap07.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/92b24663-f692-b3d7-9a85-79e677048dcd.png)

次へをクリックし、進みます。

### ユーザープール作成 ステップ4

ステップ3で有効化した検証メッセージの送信方法を設定します。
テストなので、簡単なSESを使用しない設定にします。

![cap08.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/6aef16f1-2d00-503b-aba9-d16adddb028b.png)

その他の設定はデフォルトのままにしておきます。
次へをクリックし、進みます。

### ユーザープール作成 ステップ5
ユーザープールの設定を行います。
名前とホストされた認証ページを用意するかを選択します。
認証ページはreactで作成するので、チェックを外します。

![cap09.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/fb19f6ae-fb49-0152-8fe1-4805f2e9a5ab.png)

今回のreactアプリ用のアプリケーションクライアントを設定します。

スイッチロールのようなイメージで、アプリケーションクライアントを複数用意することで、ユーザープール内のユーザーに対し、複数の認証フローやOpenID Connect のスコープを指定することができます。

クライアント名を指定します。

![cap10.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/c865eebf-7de6-7014-a761-c577bfda24ec.png)

テストなのでクライアントシークレットは不要としておきます。

※参考

https://qiita.com/nisim/items/dd184c614923855d71f9

認証フローはデフォルトのままとしておきます。

![cap11.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/6267473d-e31b-3b13-ad9e-ead8fe11fea4.png)

![cap12.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/c0e91b61-2dff-ed4c-a635-f0883849b83d.png)

次へをクリックし、進みます。

### ユーザープール作成 ステップ6

![cap13.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/e77495e7-1328-6775-9c57-71c900d626ad.png)

![cap14.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/18a16eaf-6eb4-c48c-8c30-90ec34bec62e.png)

問題なければ、ユーザープールを作成します。

## ユーザープールIDとアプリケーションクライアントIDを取得する

アプリで使用するので、ユーザープールIDとアプリケーションクライアントIDを取得します。

マネジメントコンソールから取得します。

![cap15.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/c6cff2c2-96d9-3241-0a3c-7dd82c7cbdc1.png)

アプリケーションクライアントのクライアントIDも同様に取得します。

![cap16.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/1ba4c375-f686-bcdb-c48c-e42dba6c192a.png)

![cap17.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/2a68b6e8-0037-9dff-fe22-8dc0b0c580c9.png)

Cognitoの初期設定はここまでで完了です。

## 必要なライブラリのインポート

### reactプロジェクトの用意

```terminal
npx create-react-app react-cognito --template typescript
```

### 必要なライブラリのインポート
aws amplifyライブラリが必要なのでインポートします。
※amplify appは作成せず、関数だけ利用します。

```terminal
npm i aws-amplify
```
## 実装後の認証フローを整理する
アプリは以下のドキュメントのフローでユーザーを作成・確認します。

1. ユーザーは、アプリで作成した [Create an account] (アカウントを作成) ボタンを選択します。E メールアドレスとパスワードを入力します。
1. アプリケーションは SignUp API リクエストを送信し、ユーザープールに新しいユーザーを作成します。
1. アプリケーションは、ユーザーに E メールの確認コードを求めます。ユーザーは、E メールメッセージで受け取ったコードを入力します。
1. アプリは、ユーザーの確認コードを含む ConfirmSignUp API リクエストを送信します。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pools-API-operations.html#:~:text=Amazon%20Cognito%20%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%83%97%E3%83%BC%E3%83%AB%20API%20%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E3%82%B7%E3%83%8A%E3%83%AA%E3%82%AA%E4%BE%8B

早速コードを準備してテストしてみます！！

## reactコードを準備してテストする
index.tsxに以下のコードを追加し、Cognito ユーザープールへの接続設定を行います。

```src/index.tsx
// 追加コードここから
import { Amplify } from 'aws-amplify';

Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: process.env.REACT_APP_USER_POOL_ID!,
      userPoolClientId: process.env.REACT_APP_USER_POOL_CLIENT_ID!,
    }
  }
})
// 追加コードここまで

// ここからデフォルトのindex.tsx
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
```

また、以下のコードをsrcに追加し、テスト用ページを表示します。
```test.tsx
import React, { useState } from 'react';
import { signUp, confirmSignUp, signIn, getCurrentUser, signOut } from 'aws-amplify/auth';

const Test: React.FC = () => {

    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [confirmationCode, setConfirmationCode] = useState('');

    const handleSignUp = async () => {
        // signUp関数を使用する。
        // アカウントはUnconfirmed状態作成され、別途有効化する必要がある。
        const output = await signUp({
            username: email,
            password,
        })
        console.log(output)
    };

    const handleVerify = async () => {
        // 入力されたコードを検証し、ユーザーの有効化を行う
        const output = await confirmSignUp({
            username: email,
            confirmationCode,
        })
        console.log(output)
    };

    const handleLogin = async () => {
        // 入力されたemail, passwordを使用し、ユーザーのログイン処理を行う
        await signIn({
            username: email,
            password,
        })

        const output = await getCurrentUser()
        console.log(output)
    }

    const handleLogout = async () => {
        await signOut()
    }

    return (
        <div className="App" style={{ maxWidth: "900px", margin: "auto", padding: "10px 50px" }}>
            <h1>Cognito Test</h1>
            <h2>SignUp</h2>
            <div>
                <label htmlFor="email">登録メールアドレス：</label>
                <input
                    id="email"
                    type="text"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                />
            </div>
            <div>
                <label htmlFor="password">パスワード：</label>
                <input
                    id="password"
                    type="text"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                />
            </div>
            <div>
                <button onClick={handleSignUp}>検証用コードをメールアドレスに送信する</button>
            </div>
            <h2>VerifyUser</h2>
            <div>
                <label htmlFor="confirmationCode">検証コード：</label>
                <input
                    id="confirmationCode"
                    type="text"
                    value={confirmationCode}
                    onChange={(e) => setConfirmationCode(e.target.value)}
                />
            </div>
            <div>
                <button onClick={handleVerify}>コードを検証する</button>
            </div>
            <h2>Login/Logout</h2>
            <div>
                <button onClick={handleLogin}>ログインする</button>
            </div>
            <div>
                <button onClick={handleLogout}>ログアウトする</button>
            </div>
        </div>
    );
};

export default Test;
```

## コード解説
### signUp関数
フロントエンドのクライアントに、ユーザープールへの登録をさせるメソッドです。
signUp関数は上記したフローのステップ2を実行します。

> 2. アプリケーションは SignUp API リクエストを送信し、ユーザープールに新しいユーザーを作成します。

※参考：詳しいドキュメント

https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-up/


### confirmSignUp関数
confirmSignUp関数は上記したフローのステップ4を実行します。
今回は、クライアントにメールを確認してもらい、メール内にあるコードを受け取り、リクエストボディとしてます。

> アプリは、ユーザーの確認コードを含む ConfirmSignUp API リクエストを送信します。

※参考：詳しいドキュメント

https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-up/#confirm-sign-up

### signIn関数
そのままの関数です。

※参考：詳しいドキュメント

https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/

### getCurrentUser関数
signIn状態の場合、ユーザー情報を取得できます。
今回はテスト用に実行します。

※参考：詳しいドキュメント

https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/manage-user-sessions/#retrieve-your-current-authenticated-user


## テスト
作成したテストページで動作をテストします。

![cap18.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/847bf6ae-2529-6307-19d9-d07fcf4f4550.png)

### SignUpしてみる
メールアドレスとパスワードを入力し、SignUp関数を実行します。

![cap19.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/2f23a51c-9b38-6735-5359-5448677b3618.png)

関数の戻り値をコンソールに出力しています。

![cap20.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/079eb6c5-9b6f-2e86-31cc-91a984e699d0.png)

SignUpが完了していないことや、Confirmが必要なことがわかります。

Cognitoユーザープールに保存されているユーザー情報も確認してみます。

![cap20_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/0463c03d-f7b6-d08c-6f40-dc748c4f9127.png)

ユーザーが登録できてます！！！
ただ、確認ステータスが未確認なことがわかります。

メーラーを確認します。

![cap21.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/7750c861-6ac5-4e5f-5d73-5f8a481014c8.png)

検証コードが送信されていることがわかります。

### Confirmしてみる
メールされた検証コードを使用し、SignUpを完了してみます。
入力した検証コードとemailでconfirmSignUp関数を実行します。

![cap22.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/581f85b0-fe43-2249-3e2f-bcec41218a30.png)

関数の戻り値をコンソールに出力しています。

![cap23.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/4a415ab5-787a-32df-638b-c9275ad841ff.png)

SignUpが完了していそうです！！

Cognitoユーザープールに保存されているユーザー情報も確認してみます。

![cap24.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/9e5dbc7b-ea47-2115-ff05-19bcb33f08cc.png)

確認ステータスが確認になっていることがわかります！！！！

### SignInしてみる
SignIn関数とgetCurrentUser関数を実行してみます。

![cap25.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654730/70742cf4-536b-4a55-6f09-512f43a2b393.png)

エラーも出ず、ユーザー情報が取れました！！！

# おわりに
CognitoとReactでユーザーのサインアップ～ログインまで動作確認しました。
Cognitoは複雑なイメージがあったのですが、簡単に疎通させることができました！！

メールに認証用リンクを乗せて、リンクを踏むだけで検証完了とするつくりを目指したいので、次はその仕組みを考えようかなと思います！