Nuxt3 + AWS Cognito スターターセット

# 初めに
仕事で認証系を実装することになったが、cognito等を使って外に出した方が実装コストや保守運用も楽になるので、簡単な実装ガイドとして本記事を書く

コードは以下のリポジトリ

https://github.com/sandy-sunday/nuxt3_cognito_tutorial

# 準備
## AWS Cognito

まず、認証用のユーザープールをaws cognitoで作成する必要がある。
Amazon Cognito > ユーザープール > ユーザープールを作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/c252b478-44a6-19f8-f5a1-1325c53ff607.png)


> ユーザープールの作成後に Cognito ユーザープールのサインインオプションを変更することはできません。

cognito関連の設定は変更が効かないので、慎重に。
今回はメールアドレスとパスワードのみのシンプルな認証なので以下で設定

**認証プロバイダー**
プロバイダータイプ：Cognito ユーザープール
Cognito ユーザープールのサインインオプション：Eメール

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/a7f927f0-2faf-5611-da6a-91eb523f7e1f.png)

**パスワードポリシー**
パスワードポリシーモード：Cognito のデフォルト

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/485088d2-f1d1-7a2d-923f-71c1644de069.png)

**多要素認証**
MFA の強制：MFAなし（本当は必要だよ）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/5f500805-102a-bda2-6227-7f94fb78db77.png)

**ユーザーアカウントの復旧**
セルフサービスのアカウントの復旧情報：セルフサービスのアカウントの復旧を有効化 - 推奨
ユーザーアカウント復旧メッセージの配信方法：Eメールのみ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/27f88ca7-7ea7-8bda-9e02-2cc0d5ff93ac.png)

**サインアップエクスペリエンスを設定**
セルフサービスのサインアップ:自己登録を有効化

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/c52a87fc-541e-e85e-427f-0b780fbd6d1b.png)

**属性検証とユーザーアカウントの確認**
検証する属性: E メールのメッセージを送信、E メールアドレスを検証
属性変更の確認: 未完了の更新があるときに元の属性値をアクティブに保つ - 推奨
未完了の更新があるときのアクティブな属性値: E メールアドレス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/78f68ae9-0879-f085-a9ca-fc543cdfd50f.png)

追加の必須属性: 無し

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/2b10dfae-0991-2415-e44b-036f9f15dd4f.png)

**メッセージ配信を設定**
E メール: Cognito で E メールを送信
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/5a423d75-132f-2d96-898c-e3d0ac1bc849.png)

ユーザープール名: 任意

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/84bfa566-e16a-945b-ce78-01bf30804305.png)

ホストされた認証ページ: 無効

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/e67d9dd5-4051-a222-80e8-41dd94b90106.png)

アプリケーションタイプ: パブリッククライアント
アプリケーションクライアント名: 任意
クライアントシークレット: クライアントのシークレットを生成しない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/bc1492e0-41f6-079e-267e-1ee27048a6e3.png)

ユーザープールIDとクライアントIDを保存しておく

## コード
何はともあれgit clone
```bash
git clone https://github.com/sandy-sunday/nuxt3_cognito_tutorial.git
```

環境変数のテンプレートをコピーして、先ほど保存したユーザープールIDとクライアントIDをコピぺする。
```bash
cp .env.example .env
```

```bash
NUXT_PUBLIC_USER_POOL_ID=your_user_pool_id
NUXT_PUBLIC_CLIENT_ID=your_client_id
```

必要パッケージをインストールしてサーバーを起動
```bash 
yarn install
yarn dev
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956485/3fa8e4cb-c8f2-1a6d-ba33-7af500869aae.png)

```localhost:3000``` にアクセスして画面が表示されたら成功。

# コード補足
## cognitoで作ったユーザーでログインできない
実装時に詰まった部分として、cognitoのコンソール上で作成したユーザーで初めてログインしようとしたら、"Error: New password is required."と出てログインに失敗することがあった。この状況で```cognitoUser.forgotPassword()```を使っても、認証コードが送られてくることは無く、cognitoコンソール上で「E メールアドレスを検証済みとしてマークする」にチェックを入れても解消されることは無かった。

どうやら初回ログインのみ```cognitoUser.completeNewPasswordChallenge()```でパスワードを設定する必要がある。

```ts
// /pages/login.ts
  cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function() {
      window.location.href = '/login-success';
    },
    newPasswordRequired: function(userAttributes, requiredAttributes) {
      showNewPasswordInput.value = true;
      buttonLabel.value = 'Reset Password';
      
      // On initial login, the user must change their password
      cognitoUser.completeNewPasswordChallenge(newPassword.value, {}, {
        onSuccess: function() {
          window.location.href = '/login-success';
        },
        onFailure: function(err) {
          console.log("Error:", err);
        }
      });
    },
    onFailure: function(err) {
      console.log("Error:", err);
    }
  });
```

## global is not defined
cognito 関連の設定は```/plugins/cognito.ts```に実装している。プラグインとして、各ページで利用できるようにしているのだが、JavaScriptのグローバルオブジェクトは、ブラウザ上とNode.js上では異なり、ブラウザ上では”window”、Node.js上では”global”が対応するため、```yarn dev```で実行しようとすると```500 global is not defined```と出てしまう。

```ts
// /nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true },
  vite: {
    define: {
      'window.global': {}, // In SSR, window is not defined. This is a workaround.
    },
  },
  .....
  ...
```
対策として、globalに空オブジェクトを定義してあげるとエラーを回避できる。詳しくは下の記事を参照

https://zenn.dev/usuijuice/articles/0dd2afa1cd6363

## 環境変数の設定
```ts
// /nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      userPoolId: '',
      clientId: '',
    }
  },
});
```
```.env```を用意して環境変数に指定する方法を忘れていたため、記載。```NUXT_PUBLIC_USER_POOL_ID```のように接頭にNUXTを付けると環境変数として認知される。続けて```PUBLIC```のようにネストすることも可能。nuxt3からはdotenvがプリインストールされているようで設定が楽になってる。

https://nuxt.com/docs/guide/directory-structure/env#env-file

## ログインにリダイレクト
```ts
// /middleware/redirect-login.ts
export default defineNuxtRouteMiddleware((to,from) => {
    return navigateTo('/login')
  })
```

```html
<!-- /pages/index.vue -->
<template>
    <h1>Landing Page</h1>
</template>

<script setup lang="ts">
  definePageMeta({
    middleware: ['redirect-login'] // redirecting to login page if not logged in
  })
</script>
```

デフォルトのランディングページである```index.vue```はログイン画面にリダイレクトするよう設定した。実際のWebサービスでは認証トークンに有効期限を設定して、有効期限が切れたらログインないしトップページに戻す為、先行実装。