Cognito(Amplify v6) + React.jsで作るログイン・ソーシャルログイン機能

自分の備忘録、アウトプットの意を込めて。
超初心者なので間違ってる部分はご指摘いただけると大変助かりますm(__)m

## この記事でわかる事
- [Cognitoの概念について](#概念のお勉強)
- [React環境下でのCognito(Amplify v6)の実装方法](#react環境下でのcognitoの実装方法)
- [Googleアカウントでのソーシャルログイン方法](#ソーシャルログイン)

## 各種環境
**npm**
```
10.5.0
```

**node**
```
v21.7.2
```

**react**
```
^18.2.0
```
**aws-amplify**
```
^6.3.0
```

# 概念のお勉強
- [そもそもCognitoって何？](#そもそもCognitoって何？)
- [認証と認可の違い](#認証と認可の違い)
- [ユーザープール](#ユーザープール)
- [アイデンティティプール](#アイデンティティプール)

### ・そもそもCognitoって何？
> #### Amazon Cognito とは
>Amazon Cognito はウェブアプリとモバイルアプリ用のアイデンティティプラットフォームです。これは、ユーザーディレクトリ、認証サーバー、OAuth 2.0 アクセストークンと AWS 認証情報の承認サービスです。Amazon Cognito を使用すると、組み込みのユーザーディレクトリ、エンタープライズディレクトリ、Google や Facebook などのコンシューマー ID プロバイダーからユーザーを認証および承認できます。

引用

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

要するに、ウェブおよびモバイルアプリで、ユーザの認証、認可の管理を行いたいときに、Cognitoを使うと一元管理できて便利だね!ってコト:sunny:

更に、Cognitoを使うとGoogle、FaceBook、Amazon、Lineなどのソーシャルアカウントでのログインが簡単に実装出来たり、多要素認証のサポートをしてくれたり、何かと拡張性が高い。

### ・認証と認可の違い

#### 
:::note warn
**認証**
ログインしたユーザーが確かに存在することを確かめる。
=> ユーザープール

**認可**
ログインしたユーザーが、どういう権限を持っているの確認したり、権限を選択して与える。
=> IDプール
:::

### ・ユーザープール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/38d148cc-f213-ab62-1ef2-4dd1cb850cf2.png)
引用: https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html

Cognitoで「認証」を司る部分。

**~流れ~**
1. ユーザーがパスワード等の情報を入力し、これをCognitoのユーザープールに対して認証リクエストを送信する。
2. Cognitoは、送られてきた情報が正しいか検証する。
3. 情報が正しい場合、認証が成功し、クライアントにアクセストークンがレスポンスとして返される。
4. このアクセストークンをリクエストヘッダーに含めることで、APIへのアクセスを認可することができる。 

以降、再度サイトにアクセスした際は、ローカルストレージにアクセストークン等が保存されて、これが使用可能であるかどうかでユーザー認証済みかそうでないかを判断してるっぽい？

いや、ローカルストレージに保存できてしまうのはよくないような...:thinking:


### ・アイデンティティプール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/42bff0b6-6908-aed2-677a-02ee7bb182af.png)
Cognitoで「認可」を司る部分。

認証を行ったユーザーに対して一時的なクレデンシャルキーを付与することで、ユーザーがどのリソースにアクセスできるのかを管理できるのがこれ。

↓参考になりました。

https://dev.classmethod.jp/articles/get-aws-temporary-security-credentials-with-cognito-id-pool-by-aws-cli/

# react環境下でのcognitoの実装方法

プロジェクト作成手順は省略

## 1. AWS

**1. AWS Cognitoにアクセスし、「ユーザープールを作成」ボタンを押下してください。**
https://ap-northeast-1.console.aws.amazon.com/cognito/v2/home?region=ap-northeast-1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/0a9431ea-89d5-009d-711e-5602c421c06b.png)

**2\. サインインエクスペリエンスを設定**
認証プロバイダーはそのままで、下のEmailにチェックを入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/c130fa45-066a-0d42-47fc-c4ace99a4e61.png)

**3\. セキュリティ要件を設定**
次のページは、多要素認証だけ無しにして、他はそのままで大丈夫です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/e48cf483-7211-ec5e-29b4-21919e34dc70.png)

**4\.サインアップエクスペリエンスを設定**
何もいじらなくて大丈夫です。

**5\. メッセージ配信を設定**
メッセージ配信は「CognitoでEメールを送信」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/9400fa68-0cfa-58b6-60b1-8cde0853ee9c.png)

**6\. アプリケーションを統合**
ユーザープール名とクライアント名はご自由に。
ホストされた認証ページをチェックし、Cognitoドメインを使用するを選択します。ドメイン名はご自由に。
コールバックURLは認証後にリダイレクトさせたいURLを記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/2d441ab3-e5fc-a10a-c6a6-067dcbbd8381.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/e363cbe2-5865-7356-3248-88e86faca102.png)


**7. 各種IDをメモする。**
ユーザープールに作成したものが表示されるので、ユーザープール名をクリックして詳細画面に行き
・**ユーザープールID**
・**クライアントID** (アプリケーションの統合タブから確認できる)

をメモしておいてください。
また、「アプリケーションの統合」タブ下部から、アプリクライアント詳細に飛べるので、詳細画面から追加の設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/4153fc7a-7cc5-5bc2-1a29-599bfcc89d7b.png)

**8. アプリクライアント詳細画面、「ホストされたUI」の編集ボタンから、認証に関する設定**

**・サインアウトURL**
ログアウト後に遷移するURLを設定できます。

**・IDプロバイダー**
ログイン認証に用いるプロバイダーを選択できます。今はソーシャルログインの設定をしていないので、「Cognitoユーザープール」以外はないです。

**・OpenID Connectのスコープ**
アクセストークンに含めるためのスコープを指定できます。
- openID
- Email
- profile
を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/a909eade-c6fe-95dc-6b00-cdb1867f07d4.png)



**10\. ユーザープール画面に戻り、「メッセージング」タブ最下部から、検証メッセージを選択し、編集します。**

もともと**code**が選択されているので、<font color="#ff0000">**link**</font>に変えておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/aacea6bf-2071-e940-37d5-9d5ebab8f787.png)

___
お疲れ様でした!AWS側の作業は以上です。
次にReact側に移ります!
## 2. React
aws-amplifyを使うのでインストールします。
```
npm i aws-amplify
```

まず、index.tsxを以下のように書き換えてください。
```react:index.tsx
Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: String(process.env.REACT_APP_AWS_USER_POOLS_ID),//メモっておいたユーザープールIDをここに!
      userPoolClientId: String(process.env.REACT_APP_AWS_USER_POOLS_CLIENT_ID),//メモっておいたクライアントIDをここに!
      loginWith: {
        oauth: {
          domain: 'XXXX.auth.ap-northeast-1.amazoncognito.com',//Cognitoドメイン(XXXX => 作成したドメイン名を挿入)
          scopes: ['openid', 'email', 'profile'],
          redirectSignIn: ['http://localhost:3000/login/home'],//ログイン後、リダイレクトするurl
          redirectSignOut: ['http://localhost:3000/login'],//ログアウト後、リダイレクトするurl
          responseType: 'code',
          providers: ['Google', { custom: 'Line' }]//後述(ソーシャルログイン)
        }
      }
    },
  }
});

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```
#### ・ログイン処理
```react
const handleLogin = async (username: string, password: string) => {
    try {
      await signIn({ username, password });
    } catch (error) {
      //~~~
    }
  };
```
フォーム等でusername(メールアドレス)、passwordを入力させて、それを送信します。
シンプルですね。

#### ・ログアウト処理
```react
  const handleLogout = async () => {
    const currentUser = await getCurrentUser();
    if (currentUser) {
      await signOut();
      localStorage.clear();
    } else {
      localStorage.clear();
    }
  };
```
getCurrentUser()は、ログイン中のユーザーを取得する関数です。ユーザーがいればログアウトし、いなかった場合も、念のためローカルストレージを消しておきます。

#### ・新規ユーザー登録
```react
const handleCreateAccount = async (username: string, password: string) => {
    try {
      await signUp({ username, password });
      alert('認証用のメールを送信しました。ご確認ください。');
    } catch (error) {
      //~~~
    }
  };
```
ログインと同様、メールアドレスとパスワードを送信します。先ほど認証方法を**リンク**に変更したので、入力したメールアドレス宛に認証リンク付きメールが届きます。

そちらを踏むと、新規ユーザー登録完了となります。

#### ・パスワード変更
```react
const handleForgotPassword = async (mail: string) => {
    setUser(mail);
    try {
      resetPassword({ username: mail });
      alert('入力されたメールアドレスに認証コードを送信しました。ご確認ください。');
    } catch (error) {
      //~~~
    }
  };

  const handleResetPassword = async (confirmPass: string, password: string, code: string) => {
    try {
      if (isEqual(confirmPass, password)) {
        await confirmResetPassword({
          username: location.state.mail,
          newPassword: password,
          confirmationCode: code
        });
      } else {
        //~~~
      }
    } catch (error) {
      //~~~
    }
  };
```

パスワード再設定は二段階の処理になります。

1\. メールアドレスを入力させ、対象アドレスに確認コードを送信。

2\. 「メールアドレス、新規パスワード、確認コード」を入力させ、正しい情報だった場合、パスワードを新規パスワードに変更。

<sup>なぜかこいつは認証方法がコードしか対応してない...?</sup>

以上
___
# ソーシャルログイン
次に、ソーシャルログインの実装方法です。
まずはGoogle側で連携の準備を行います。

## 1\. Google Cloud Platform
**1\. https://console.cloud.google.com/welcome に行き、新しいプロジェクトを作成します。**
**2\. 左メニューから、「APIとサービス」 =>「OAuth 同意画面」 に行き、作成ボタンを押します。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/e639620e-cef4-46b2-c777-9e23dfe9ef95.png)
**3\.以下の情報を入力し保存して次へ。**
・アプリ名
・ユーザーサポートメール
・承認済みドメイン
・デベロッパーの連絡先情報

承認済みドメインは、AWS => 「ユーザープール詳細」 => 「アプリケーションの統合」タブの「Cognito ドメイン」から確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/e544eee5-4ad2-53d6-fdda-54cae6e5579d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/7a9a9127-1a6a-c248-faf7-b61b71d258e3.png)

**4\.以下の情報を入力し保存して次へ。**

・スコープを追加または削除 => **email**、 **profile**、 **openid**にチェック => 下部の更新ボタン押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/60f3c31e-8c47-81bd-47c2-a4dc3dcc2607.png)

**5\. テストユーザーは追加しなくて大丈夫です。そのまま作成完了してください。**

**ダッシュボードに戻り、左メニュー => 「認証情報」 => 「認証情報を作成」=>「OAuthクライアントID」をクリックします。**

**6\. 以下の内容を入力してください。**
・アプリケーションの種類=>ウェブ アプリケーション
・名前
・承認済みの JavaScript 生成元 => 「http://localhost:3000」
・承認済みのリダイレクトURI => https://XXXX.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse \(XXXXは自分で作成したドメイン名)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/317dcdf3-2667-55a4-136e-3784327947ef.png)

**7\. 作成し、出てきた「**クライアントID**」「**クライアントシークレット**」をコピーしておいてください。後で使います。**


## 2\. AWS
**1\. ユーザープール => 「サインインエクスペリエンス」タブから、アイデンティティプロバイダーを追加します。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/24df6399-8cbc-8d76-6a47-5e5b124ee34b.png)

**2\. アイデンティティプロバイダーは「Google」を選択し、以下の内容を入力して追加。**
・クライアントID => 先ほどメモしたクライアントID
・クライアントシークレット => 先ほどメモしたクライアントシークレット
・許可されたスコープ => **openid**、**profile**、**email**を"**スペース区切りで**"入力してください。
・Google とユーザープール間で属性をマッピング => Google属性をemailに。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/d0652732-42fb-cee2-5782-f030eb992f4e.png)

**3\. ユーザープール => 「アプリケーションの統合」タブ => アプリケーションクライアント名クリック => 「ホストされたUI」編集**
アイデンティティプロバイダーに「Google」を追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1396501/15526147-2903-dbf9-f139-79e144e4fabe.png)

## 3. React
ボタンを用意し、onClickで以下のメソッドを実行します。
```react
  const loginWithSocialAccount = async () => {
    await signInWithRedirect({
      provider: 'Google'
    });
  };
```

:::note warn
providerをGoogleにしてください。
:::

ボタンをクリックすると、ログインできるようになっているはずです。

### おまけ
providerで用意されている型が、
```ts
export type AuthProvider = 'Amazon' | 'Apple' | 'Facebook' | 'Google';
```
の4つです。しかし、openIDを用いるようなもの(Lineとか)でログインしたい場合もあります。
その場合、providerを以下のように書き換えます。
```react
await signInWithRedirect({
    provider: { custom: 'Line' }
});
```
customを挟むことで、自由に指定できるようになります。

# まとめ
Cognitoをさわり始めてまだ1週間も経ってないぐらいですが、とっかかりやすく面白い技術だなと感じています。特に、ログイン回りの記述がすっきりしているところも好感触です:relaxed:

何よりも、ログイン状態の確認が```Hub.listen```で簡単にできるのが一番好きかも:exclamation:

長くなってしまいましたが、ここまで読んでいただき、ありがとうございました!