Amazon Cognitoとvue.jsでユーザーログインできるウェブアプリを作る

この記事では[**Amazon Cognito**](https://aws.amazon.com/jp/cognito/)によるユーザー管理の機能を搭載するウェブサイトの作成サンプルを説明します。フロントエンドは**vue.js**（**TypeScript**）フレームワークでバックエンドは無しです。


# はじめに

**Amazon Cognito**はAWSのサービスの一つです。詳しくは既に色んな説明の記事があるので割愛しますが、簡単にいうとユーザーの登録やログインや情報管理をする機能を実現するための便利な方法です。

基本的にAWSに登録したらAmazon Cognitoをすぐ作成して無料で使うことができます。

普段ログインシステムを作るならバックエンドでSQLなどのデータベースが必要とするはずですが、Amazon Cognitoはその部分を準備してくれるので、バックエンドなしでフロントエンドだけでウェブアプリを作ることができて便利です。

この記事ではAmazon Cognitoの使い方を勉強するために簡単なウェブアプリを作る例を挙げます。

フレームワークなしでただのJavaScriptでもAmazon Cognitoを実装することができますが、フレームワークを使った方がやはり楽です。私はいつもvue.jsを使っているので、今回の例もフロントエンドをvue.jsで書きます。

TypeScriptで書きますが、普通にJavaScriptで書く場合も似ているので、Amazon Cognitoを使った三井人のための参考になったら幸いです。


# 目標のアプリ

どんなウェブアプリが作れるかとりあえず見ておきましょう。

まずはアクセスしたらこんな画面になります。

![caq01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/864a412f-6943-8d30-d27d-a781ae393f9a.png)

ここでログインしなければ何もできません。初めてアクセスしてまだユーザー登録氏ていない人は右下のボタンを押します。

そうしたらユーザー登録画面に切り替えます。

![caq02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9d08d6bd-1086-aa21-df01-7b9a96bb0a2b.png)

因みに左上の「Ξ」をクリックしたら左の方にユーザー登録画面とログイン画面のリンクが現れます。もう一度クリックしたら消えます。

![caq03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/7b7e818a-8123-95fd-24ac-222c05d23eee.png)

試しにユーザー登録の情報を入れて送信したら認識コードが入力したメールへ送られます。同時にこの認証の画面に切り替えます。

![caq04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/c2f7926c-6055-b207-8082-28db413e562f.png)

認証コードを入力して送信したらユーザー登録は完了です。自動的にログイン画面に戻ります。

![caq05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/016cda8f-62e1-7669-e7ac-9a24a5c25268.png)

そしてログインしたらこのような画面になります。ユーザー名と姓名も上に表示されます。

![caq06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9b39be1b-b39e-2bf2-8b02-e11a70b8d52d.png)

この画面は本来は使いたいアプリの画面ですが、今回はただログインシステムのサンプルをしたいので、アプリの中身を作らずにここで終わります。

因みに「杏」を書いたのは、杏は英語でアプリコット（apricot）だからです。「アプリ」と**co**gni**t**oだから「apricot」。ただ適当です。

その他に左の「Ξ」をクリックしたら左の選択も変わって、情報更新とパスワード変更の画面に入ることもできます。

![caq07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/946a6cc1-384e-b37b-a143-83cdbdf215fd.png)
![caq08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/df369134-04af-4275-6c39-8b752777b4b7.png)

尚、一度ログインしたら道道的にログイン状態がセッションストレージに保存されて、今後もう一度アクセスしたらそのままログインの状態です。

ログアウトしたらセッションストレージがクリアされてログイン状態が終わります。

最後にもしパスワードを忘れたらログインの画面に戻って左下のボタンを押すと認証コードが送られてパスワード再設定の画面に入ります。

![caq09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/30bfb3af-0fb6-53ed-85d2-306400592f7e.png)

以上、簡単なアプリのテストと説明です。


# Cognitoユーザープールの作成

次はAmazon Cognitoを始める方法について説明します。

まずはAWSアカウントの登録をする必要があります。AWSの登録や使い方に関してはとりあえず割愛して、既に登録していてある程度使っているという前提で話を進めます。

検索の欄で「cog」などで検索してみたらCognitoが見つかります。

![caqa01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/26ad2082-c8be-a4a1-3fd3-9e9a558154bd.png)

Cognitoの最初の画面に入ったら「ユーザープールを作成」をクリックします。

![caqa02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/86d5c8fb-12f1-ab06-70a4-e144b2661536.png)

ユーザープールというのは、簡単にいうとユーザーの情報を登録するデータベースです。ユーザーのデータはAWSのサーバーに保存されて、これに接続することでデータ追加や取得や更新や削除ができます。

だからCognitoの使用はまずユーザープールの作成から始めます。

作成画面です。

![caqa03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9b677b6d-04e9-7592-87d4-8166e3ff1e66.png)

選択の項目が多くて今すぐ理解する必要がないと思うので、私が調整した部分だけ姓名します。その他は特にわからなければ既定値のままでいいです。

まずサインインオプションはユーザー名だけか電子メールや電話番号でログインすることもできますが、ここではユーザー名だけにしておきます。そしてユーザー名は制限なしどんな名前も使えるようにします。大文字と小文字の区別もちゃんとします。

![caqa04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/5aadb0e6-bef8-c6fb-f995-35be7ab32640.png)

次はパスワードの制限です。既定値のままだと制限が多くてややこしいので、全部の制限を外してどんなパスワードも使えるようにします。最小文字数は既定値の8のままでいいです。

![caqa05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/b442b8a8-af03-74b2-87e0-4431cd47cbde.png)

多要素認証はなしにします。

![caqa06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/eb131537-5b0d-5012-40f3-ea885b59b4b0.png)

ユーザーアカウントの復旧は電子メールでできるように設定します。

![caqa07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/64ca93a9-fb23-1a0c-53d8-569f5e7f4202.png)

次は登録したいユーザーの属性です。以前の選択によって電子メールは既に必須になっていますが、その他にも色々追加できます。今回は「name」つまりフルネームだけ追加します。

![caqa08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/245c3219-ef63-90ae-69da-5268a94ac984.png)

次は認証メールを送る電子メールアドレスの設定です。左がお勧めとされているようですが、今回は簡単なサンプルのため右のCognitoでいいです。

![caqa09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/ac648620-f72d-4419-12e8-cf3902118391.png)

次はユーザープールの名前です。これはあまり重要ではないので適当でいいと思います。

![caqa10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a648df92-2c05-dbf6-393b-c10875490db0.png)

それとアプリケーションクライアントの名前、これも適当でいいです。

![caqa11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/6a3a5508-2ea2-b2a3-cfdd-74ca2d984d41.png)

最後にクライアントシークレット、これも今回は生成しなくていいです。

![caqa12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/98f5b9ee-59aa-b09c-3c85-fc6cc93d1288.png)

全部の選択が終わったら確認画面に入って、「ユーザープールを作成」ボタンを押したら完成です。

![caqa13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/87cdfbf4-9d9f-e35d-1006-cd4a2fb17ba6.png)

そしてユーザープールが表示される画面に入ります。今作成したユーザープールはここにあります。ここで重要なのは「ユーザープールID」です（適当に名付けしたユーザープール名ではない）。後で使うのでここではとりあえずコピーして起きましょう。

![caqa14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/33798ab8-f75a-1454-c5af-9bb95d2c3598.png)

作成されたユーザープールの名前をクリックして詳細を調べてみます。ここで「アプリケーションの統合」の項目を探してクリックします。

![caqa15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/de1bcc33-0989-bb86-12b7-1c6552575e96.png)

そうしたら先程入力したアプリケーションクライアント名が表示されます。そしてクライアントIDもここに書いてあるのでこれも必要なものなのでコピーして起きます。

![caqa16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/8bc0f1d8-ec67-3b10-1efd-dd76ad4672dd.png)

AWS側の準備はこれで整いました。

尚、ユーザー登録をしたらすぐここのユーザーのリストに反映されます。

![caqa17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/97e56c4b-2ad8-3db1-2f69-687d2477c485.png)


# 実装

## 環境設定

次は作成しておいたユーザープールに接続してユーザー管理をするための実装です。ここではまず今回の実行で使ったパソコンの環境設定について説明しておきます。

使うライブラリーとバージョン。

<table>
<tr><td><strong>vue</strong></td><td>3.4.31</td></tr>
<tr><td>vuetify</td><td>3.6.11</td></tr>
<tr><td>vuex</td><td>4.1.0</td></tr>
<tr><td>amazon-cognito-identity-js</td><td>6.3.12</td></tr>
<tr><td>vite</td><td>5.3.3</td></tr>
<tr><td>yarn</td><td>1.22.22</td></tr>
<tr><td>typescript</td><td>5.4.2</td></tr>
</table>

[`amazon-cognito-identity-js`](https://www.npmjs.com/package/amazon-cognito-identity-js)はAmazon Cognitoに接続するためのライブラリーです。ウェブアプリを構成するコンポネントは[`vuetify`](https://vuetifyjs.com/ja/)を使います。今回は沢山コンポネントに分けられてCognitoからのユーザーデータが各コンポーネントに渡されるのでそのデータを簡単に扱うために[`vuex`](https://vuex.vuejs.org/ja/)を使います。

先月もvuetifyを使ったサンプルを作って記事を書いたのですが、あの時と比べたらバージョンが少し上がって、変化も少し見えます。

https://qiita.com/phyblas/items/d25315a2fdab744117ae


## プロジェクト作成

ここでプロジェクトの作成はyarnで行いますが、npmやbunでもできます。

```
yarn create vuetify
```

```
yarn create v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-vuetify@2.2.6" with binaries:
      - create-vuetify
[##########] 10/10
Vuetify.js - Material Component Framework for Vue

✔ Project name: … cogvueto
✔ Which preset would you like to install? › Barebones (Only Vue & Vuetify)
✔ Use TypeScript? … No / Yes
✔ Would you like to install dependencies with yarn, npm, pnpm, or bun? › yarn
✔ Install Dependencies? … No / Yes

◌ Generating scaffold...
◌ Installing dependencies with yarn...

yarn install v1.22.22
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
✨  Done in 15.56s.

cogvueto has been generated at /Users/phyblas/Library/CloudStorage/Dropbox/web/cogvueto/cogvueto

Discord community: https://community.vuetifyjs.com
Github: https://github.com/vuetifyjs/vuetify
Support Vuetify: https://github.com/sponsors/johnleider
✨  Done in 83.02s.
```

プロジェクトの名前は、cognitoとvueだから「**cogvueto**」にします。

作成する時に出てきた選択肢はここに書いておきます。

✔ Barebones (Only Vue & Vuetify)
✔ Yes
✔ yarn
✔ Yes

プロジェクトフォルダに入ります。
```
cd cogvueto
```

vuetifyの他にvuexとamazon-cognito-identity-jsが必要なので追加します。
```
yarn add vuex
```
```
yarn add v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
└─ vuex@4.1.0
info All dependencies
├─ @vue/devtools-api@6.6.3
└─ vuex@4.1.0
✨  Done in 1.22s.
```

```
yarn add amazon-cognito-identity-js
```
```
yarn add v1.22.22
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 18 new dependencies.
info Direct dependencies
└─ amazon-cognito-identity-js@6.3.12
info All dependencies
├─ @aws-crypto/sha256-js@1.2.2
├─ @aws-crypto/util@1.2.2
├─ @aws-sdk/util-utf8-browser@3.259.0
├─ @smithy/types@3.3.0
├─ amazon-cognito-identity-js@6.3.12
├─ base64-js@1.5.1
├─ buffer@4.9.2
├─ fast-base64-decode@1.0.0
├─ ieee754@1.2.1
├─ isarray@1.0.0
├─ isomorphic-unfetch@3.1.0
├─ js-cookie@2.2.1
├─ node-fetch@2.7.0
├─ tr46@0.0.3
├─ tslib@2.6.3
├─ unfetch@4.2.0
├─ webidl-conversions@3.0.1
└─ whatwg-url@5.0.0
✨  Done in 2.05s.
```

これで基本的なプロジェクトの準備は完了です。次は自分でコードを書く段階に入ります。

## 実装のコード

完成した後のプロジェクトフォルダはこんな感じになります。

![caq18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/1308ac74-3425-b300-2c1b-9a0f3de68cb7.png)

ここで青になっている部分は自分で作成したファイルと編集した部分が含まれるファイルです。その以外はそのままで何かする必要がありません。

App.vueは最初からあるファイルですが、殆ど最初から書き直しです。componentsフォルダの中の.vueは全部自分で作ったコンポネントです。コンポネントは全部10ですが、それぞれそこまで大きくはありません。ただわかりやすいようにできるだけ細かく分けるだけ。

### index.html

まずはindex.htmlです。これは元からあった重要なファイルですが、特に変更する必要なくそのまま使ってもいい場合も多いですが、今回重要なのは `window.global = window;`を入れるというところです。これがないとエラーになります。これについてこの記事に説明があります。

https://qiita.com/tatmius/items/f28369bb8d10324f6b7b

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <link rel="icon" href="/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>≈cogvueto≈</title>
</head>

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>

</html>

<script>
  window.global = window;
</script>
```

### src/main.ts

ここにcogintoのクラスを定義してインスタンスを作成して、各コンポネントから使えるようにvuexのstoreに渡します。

`CognitoUserPool`を作成する部分の`UserPoolId`と`ClientId`は予めユーザープールを作成した時に準備しておいたユーザープールIDとクライアントIDを入れます。（以下のコードの中に書いてあるのは本物ではなくそれっぽい適当なIDです）

`Storage: sessionStorage`の指定は、ログイン状態のデータをセッションストレージに保存するためです。指定しないままのデフォルトではローカルストレージに保存されるということになりますが、セッションストレージの方がいいです。

```typescript:src/main.ts
import { registerPlugins } from '@/plugins';
import App from './App.vue';
import { createApp } from 'vue';

const app = createApp(App);
registerPlugins(app);

import { createStore } from "vuex";
import { CognitoUser, CognitoUserPool } from "amazon-cognito-identity-js";

class Cognito {
  user_pool: CognitoUserPool;
  user: CognitoUser | null;
  username: string = "";
  fullname: string = "";
  email: string = "";
  sousa: string = "";
  login_shita: boolean = false;

  constructor() {
    this.user_pool = new CognitoUserPool({
      UserPoolId: "ap-northeast-3_kNoREwYnI",
      ClientId: "oq5h2pl3i9600jni2a0j155g77",
      Storage: sessionStorage
    });
    this.user = this.user_pool.getCurrentUser();
  }

  set_user() {
    this.user = new CognitoUser({
      Username: this.username,
      Pool: this.user_pool,
      Storage: sessionStorage,
    });
  }

  get_attr() {
    this.user!.getUserAttributes((err, res) => {
      if (!err && res) {
        res.forEach((attr) => {
          if (attr.Name == "name") { this.fullname = attr.Value; }
          else if (attr.Name == "email") { this.email = attr.Value; }
        });
      }
      else { alert(err); }
    });
  }
}

const store = createStore({
  state: {
    cognito: new Cognito()
  }
});
app.use(store);
app.mount('#app');
```

### src/vuex.d.tsとsrc/store.d.ts

これはTypeScriptの型のエラーを解消するために追加されるものです。

```typescript:src/vuex.d.ts
declare module "vuex" {
  export * from "vuex/types/index.d.ts";
  export * from "vuex/types/helpers.d.ts";
  export * from "vuex/types/logger.d.ts";
  export * from "vuex/types/vue.d.ts";
}
```

```typescript:src/store.d.ts
import { ComponentCustomProperties } from "vue";
import { Store } from "vuex";

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $store: Store
  }
}
```

### src/App.vue

ウェブアプリの一番外の構造を成す部分です。ここから各コンポネントが呼び出されて構成されます。基本的に3つの部分に分けられます。

- ueno.vue：上のログイン状態を表示する`v-app-bar`
- nishino.vue：左のリストの`v-navigation-drawer`
- nakano.vue：メイン画面を構成する`v-main`

因みに上の方にあるからuenoで、左にあるからnishino……など名前は適当です。

```vue:src/App.vue
<template>
  <v-app>
    <ueno @drawer="drawer = !drawer" />
    <nishino v-if="drawer" />
    <nakano />
  </v-app>
</template>

<script lang="ts">
export default {
  data() {
    return {
      drawer: false
    };
  }
}
</script>
```

### src/components/ueno.vue

`v-app-bar`タグによって作れた上の部分です。ログインした状態ではユーザー名を表示します。

```vue:src/components/ueno.vue
<template>
  <v-app-bar class="py-0 my-0" color="green" short app>
    <v-app-bar-nav-icon @click.stop="$emit('drawer')" />
    <v-toolbar-title v-if="cognito.login_shita">
      {{ cognito.username }}（{{ cognito.fullname }}）ですが、何か？
    </v-toolbar-title>
    <v-toolbar-title v-else>名乗るほどの者ではない</v-toolbar-title>
  </v-app-bar>
</template>

<script lang="ts">
export default {
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
};
</script>
```

### src/components/nishino.vue

`v-navigation-drawer`タグから作られたメニュー。ログインした状態とログインしていない状態とは表示が違います。ここのメニューをクリックすることで画面が切り替えられます。

```vue:src/components/nishino.vue
<template>
  <v-navigation-drawer color="purple" width="150" mini-variant-width permanent>
    <v-list>
      <v-list-item v-if="!cognito.login_shita" @click="cognito.sousa = '登録'" title="新規登録" />
      <v-list-item v-if="!cognito.login_shita" @click="cognito.sousa = 'ログイン'" title="ログイン" />
      <v-list-item v-if="cognito.login_shita" @click="cognito.sousa = '情報更新'" title="情報更新" />
      <v-list-item v-if="cognito.login_shita" @click="cognito.sousa = 'パスワード変更'" title="パスワード変更" />
      <v-list-item v-if="cognito.login_shita" @click="logout_suru" title="ログアウト" />
    </v-list>
  </v-navigation-drawer>
</template>

<script lang="ts">
export default {
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    logout_suru() {
      this.cognito.login_shita = false;
      this.cognito.sousa = "ログイン";
      this.cognito.user.signOut();
    },
  },
};
</script>
```

### src/components/nakano.vue

この部分はメイン画面で、以下の7つのコンポネントが条件によって使われます。又、最初にアクセスした時にセッションストレージをチェックすることによってログインしているかどうかを確認する処理のコードもこのコンポネントの`mounted`に書いてあります。ログイン状態になっていない場合はログイン画面に入りますが、ログインしている場合はアプリのメイン画面（ここでは大きな「杏」の字のある画面）に入ります。

```vue:src/components/nakano.vue
<template>
  <v-main>
    <login v-if="cognito.sousa == 'ログイン'" />
    <touroku v-else-if="cognito.sousa == '登録'" />
    <ninshou v-else-if="cognito.sousa == '認証'" />
    <jouhou-koushin v-else-if="cognito.sousa == '情報更新'" />
    <password-henkou v-else-if="cognito.sousa == 'パスワード変更'" />
    <password-saisettei v-else-if="cognito.sousa == 'パスワード再設定'" />
    <anzu v-else-if="!cognito.sousa && cognito.login_shita" />
  </v-main>
</template>

<script lang="ts">
export default {
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  mounted() {
    let cognito = this.cognito;
    if (cognito.user) {
      cognito.user.getSession((err: Error) => {
        if (!err) {
          cognito.login_shita = true;
          cognito.username = cognito.user.username;
          cognito.get_attr();
        }
        else { alert(err); }
      });
    }
    else {
      cognito.sousa = "ログイン";
    }
  },
};
</script>
```

### src/components/login.vue

ログインの画面。

```vue:src/components/login.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px;">
    <div class="px-5 py-2 text-h5">まずはログインしてね</div>
    <div style="text-align: center;">
      <v-text-field class="mx-3 my-1" v-model="cognito.username" label="ユーザーだよ" hide-details />
      <v-text-field class="mx-3 my-1" v-model="cognito.password" label="パスワードだよ" type="password" hide-details />
      <v-btn class="mx-1 my-3 text-h6" @click="login_suru" color="primary" style="width: 300px;">
        では、ログインするよ
      </v-btn>
    </div>
    <div class="d-flex justify-center">
      <v-btn class="mx-1 my-1" @click="password_saisettei_suru" color="warning">パスワード忘れちゃった</v-btn>
      <v-btn class="mx-1 my-1" @click="cognito.sousa = '登録'" color="secondary">まだ登録してないんだが</v-btn>
    </div>
  </v-container>
</template>

<script lang="ts">
import { AuthenticationDetails } from "amazon-cognito-identity-js";

export default {
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    login_suru() {
      let cognito = this.cognito;
      if (cognito.username == "" || cognito.password == "") {
        alert("ユーザー名とパスワードを入力してから押してね");
        return;
      }
      cognito.set_user();
      cognito.user.authenticateUser(
        new AuthenticationDetails({
          Username: cognito.username,
          Password: cognito.password
        }),
        {
          onSuccess() {
            cognito.sousa = "";
            cognito.login_shita = true;
            cognito.get_attr();
          },
          onFailure(err: Error) { alert(err); },
        }
      );
    },
    password_saisettei_suru() {
      let cognito = this.cognito;
      if (cognito.username == "") {
        alert("ユーザー名を入力して");
        return;
      }
      cognito.set_user();
      cognito.user.forgotPassword({
        onSuccess: () => {
          cognito.sousa = "パスワード再設定";
        },
        onFailure: (err: Error) => { alert(err); }
      });
    }
  },
};
</script>
```

### src/components/touroku.vue

新規ユーザー登録の画面。

```vue:src/components/touroku.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px; ">
    <div class="px-5 py-2 text-h5">ユーザーがなければ作ればいい</div>
    <div style="text-align: center;">
      <v-text-field class="mx-3 my-1" v-model="cognito.username" label="ユーザーだが" hide-details />
      <v-text-field class="mx-3 my-1" v-model="cognito.password" type="password" label="パスワードだが" hide-details />
      <v-text-field class="mx-3 my-1" v-model="cognito.email" label="電子メールだが" hide-details />
      <v-text-field class="mx-3 my-1" v-model="cognito.fullname" label="姓名だが" hide-details />
      <v-btn class="mx-1 my-3 text-h6" @click="touroku_suru" color="primary" style="width: 300;">
        では、登録するぜ
      </v-btn>
    </div>
  </v-container>
</template>

<script lang="ts">
import { CognitoUserAttribute } from "amazon-cognito-identity-js";

export default {
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    touroku_suru() {
      let cognito = this.cognito;
      if (
        cognito.username == "" ||
        cognito.password == "" ||
        cognito.email == "" ||
        cognito.fullname == ""
      ) {
        alert("まだ入力していない項目があるんだけど");
        return;
      }
      const re = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
      if (!re.test(cognito.email)) {
        alert("電子メールはこんなもんではないはずだけど");
        return;
      }
      const lis_attr = [
        new CognitoUserAttribute({ Name: "email", Value: cognito.email }),
        new CognitoUserAttribute({ Name: "name", Value: cognito.fullname })
      ];
      cognito.user_pool.signUp(
        cognito.username,
        cognito.password,
        lis_attr,
        null,
        (err: Error) => {
          if (!err) {
            cognito.sousa = "認証";
          }
          else { alert(err); }
        }
      );
    },
  },
};
</script>
```

### src/components/ninshou.vue

ユーザー登録の認証を行う画面。

```vue:src/components/ninshou.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px;">
    <div class="px-5 py-2 text-h5">認証しよう</div>
    <div style="text-align: left;">
      <div class="mx-3 my-1">指定したメールに認証コードを送ったよ。</div>
      <div class="mx-3 my-1">ユーザー登録を完成させるために入力してね。</div>
    </div>
    <v-card class="align-center justify-center" style="text-align: center;">
      <div class="px-2 py-1">ユーザ：<span class="text-h6">{{ cognito.username }}</span></div>
      <v-text-field class="mx-3 my-1" v-model="ninshou_code" label="送られた認証コード" hide-details />
      <v-btn class="mx-1 my-2 text-h6" @click="ninshou_suru" color="primary" style="width: 220px;">
        認証しちゃう
      </v-btn>
    </v-card>
  </v-container>
</template>

<script lang="ts">
export default {
  data() {
    return {
      ninshou_code: ""
    };
  },
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    ninshou_suru() {
      let cognito = this.cognito;
      cognito.set_user();
      cognito.user.confirmRegistration(
        this.ninshou_code,
        true,
        (err: Error) => {
          if (!err) {
            cognito.sousa = "ログイン";
            alert("ユーザー登録完了");
          }
          else { alert(err); }
        }
      );
    },
  },
};
</script>
```

### src/components/jouhou-koushin.vue

情報更新をする画面。

```vue:src/components/jouhou-koushin.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px;">
    <div class="text-h5 px-5 py-2">ユーザー情報を変更していいの？</div>
    <div style="text-align: center;">
      <v-text-field class="mx-3 my-1" v-model="email" label="電子メール" hide-details />
      <v-text-field class="mx-3 my-1" v-model="fullname" label="姓名" hide-details />
      <v-btn class="mx-3 my-1 text-h6" @click="koushin_suru" color="primary" style="width: 300px;">
        情報更新しちゃうよ
      </v-btn>
      <v-btn class="mx-3 my-1 text-h6" @click="cognito.sousa = ''" color="warning" style="width: 300px;">
        やはりこのままでいいや
      </v-btn>
    </div>
  </v-container>
</template>

<script lang="ts">
import { CognitoUserAttribute } from "amazon-cognito-identity-js";

export default {
  data() {
    return {
      email: "",
      fullname: ""
    };
  },
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    koushin_suru() {
      let cognito = this.cognito;
      cognito.user.getSession((err: Error) => {
        if (!err) {
          cognito.user.updateAttributes(
            [
              new CognitoUserAttribute({ Name: "email", Value: this.email }),
              new CognitoUserAttribute({ Name: "name", Value: this.fullname })
            ],
            (err: Error) => {
              if (!err) {
                cognito.sousa = "";
                cognito.get_attr();
                alert("情報更新完了");
              }
              else { alert(err); }
            }
          );
        }
        else { alert(err); }
      });
    },
  },
  mounted() {
    this.email = this.cognito.email;
    this.fullname = this.cognito.fullname;
  }
};
</script>
```

### src/components/password-henkou.vue

パスワード変更をする画面。

```vue:src/components/password-henkou.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px;">
    <div class="text-h5 px-5 py-2">パスワードを変更したいの？</div>
    <div style="text-align: center;">
      <v-text-field class="mx-3 my-1" v-model="imano_password" type="password" label="今のパスワード" hide-details />
      <v-text-field class="mx-3 my-1" v-model="aratana_password" type="password" label="新たなパスワード" hide-details />
      <v-btn class="mx-3 my-1 text-h6" @click="henkou_suru" color="primary" style="width: 300px;">
        パスワード変更しちゃうよ
      </v-btn>
      <v-btn class="mx-3 my-1 text-h6" @click="cognito.sousa = ''" color="warning" style="width: 300px;">
        やはりこのままでいいや
      </v-btn>
    </div>
  </v-container>
</template>

<script lang="ts">
export default {
  data() {
    return {
      imano_password: "",
      aratana_password: ""
    };
  },
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    henkou_suru() {
      let cognito = this.cognito;
      cognito.user.getSession((err: Error) => {
        if (!err) {
          cognito.user.changePassword(
            this.imano_password,
            this.aratana_password,
            (err: Error) => {
              if (!err) {
                cognito.sousa = "";
                alert("パスワード変更完了");
              }
              else { alert(err); }
            }
          );
        }
        else { alert(err); }
      });
    },
  }
};
</script>
```

### src/components/password-saisettei.vue

パスワードを忘れた場合、メールで送られた認証コードを入力して再設定を行うための画面。

```vue:src/components/password-saisettei.vue
<template>
  <v-container class="px-1 py-1" style="max-width: 500px;">
    <div class="text-h5 px-5 py-2">うっかりパスワードを忘れたあなたのため</div>
    <div style="text-align: center;">
      <v-text-field class="mx-3 my-1" v-model="ninshou_code" label="認証コード" hide-details />
      <v-text-field class="mx-3 my-1" v-model="cognito.password" type="password" label="新しいパスワード" hide-details />
      <v-btn class="mx-3 my-1" @click="saisettei_suru" color="primary" style="width: 300px;">
        パスワード再設定しちゃうよ
      </v-btn>
    </div>
  </v-container>
</template>

<script lang="ts">
export default {
  data() {
    return {
      ninshou_code: ""
    };
  },
  computed: {
    cognito() {
      return this.$store.state.cognito;
    }
  },
  methods: {
    saisettei_suru() {
      let cognito = this.cognito;
      cognito.user.confirmPassword(this.ninshou_code, cognito.password, {
        onSuccess: () => {
          cognito.sousa = "ログイン";
          alert("パスワード再設定完了");
        },
        onFailure: (err: Error) => { alert(err); }
      });
    }
  }
};
</script>
```

### src/components/anzu.vue

アプリを使用するメイン画面。本来この画面に入ってアプリを使用するためにログインするので、本番ではこの部分こそメインとなるはずですが、今は「杏」の字以外何も入れられていません。

```vue:src/components/anzu.vue
<template>
  <v-container class="px-1 py-1" style="width: 500px; text-align: center; font-size: 300px; 
    background-color: brown; color:lightsalmon;">
    杏
  </v-container>
</template>
```

以上、全部のコンポネントが揃いました。それぞれ役割があって一つのアプリを構成します。

ログイン機能を入れるために色々な操作が関わって複雑になりますが、このようにわかりやすくコンポネントを分けられるのもvue.jsの便利なところです。


# 参考

### 基本とユーザープールの作成
- [Amazon Cognitoを試してみた（ユーザプール編）](https://qiita.com/zumax/items/6937b3ecb501b6ca50bb)
- [Cognitoユーザープール構築手順](https://qiita.com/Naoki1126/items/51fc6467157f4e6ee01e)
- [【AWS】Congnitoを用いたログイン機能](https://qiita.com/Mikeinu/items/cf9e468c86a1aab4f03a)
- [AWS Cognito とは](https://qiita.com/miyuki_samitani/items/609096e4e7b0a1ee08da)

### JavaScriptによる実装
- [Amazon Cognitoを使ったユーザー管理：メリットと注意点](https://qiita.com/pdc-ujoy/items/b883df3e38372469b43d)
- [【AWS】Amazon Cognito〜ログイン画面(HTML/Javascript)のハンズオン〜](https://qiita.com/kiton/items/071c655a734277f5b841)
- [Javascriptのシンプルな構成でAWS Cognitoを理解する](https://qiita.com/TKFM21/items/272f1c7be11ea274a4ee)
- [【Cognito】Vue.jsとCognitoを用いたログイン機能を実装する](https://bftnagoya.hateblo.jp/entry/2021/10/07/165307)
- [Amazon Cognitoを使ってシンプルなログイン画面を作ってみる](https://qiita.com/fkooo/items/660cab0090a80861155b)
- [Angular+Cognitoのユーザー認証付きSPAのサンプル](https://qiita.com/daikiojm/items/c90e9eb7b19f0711c829)
- [javascriptでCognitoユーザプール認証](https://qiita.com/tamo_breaker/items/2cba901565a4fe9dff1a)

### vue.jsによる実装
- [[備忘録] Cognito と Vue.js でログイン機能を実装](https://qiita.com/m-chan/items/f9df1b8c32ae7f18ce1d)
- [vue+aws amplifyのプロジェクトをviteで立ち上げたら表示が消えた(global is not defined)](https://qiita.com/tatmius/items/f28369bb8d10324f6b7b)
- [vue-cliで作成したSPAにシンプルにCognitoログインを組み込む](https://qiita.com/daikiojm/items/b02c19cfea6766c308ca)
- [VueにAWS Cognitoのログインをamplifyを使わないで作り込む](https://blog.u-chan-chi.com/post/vue-cognito/)
- [AWSサーバーレスで認証認可を最短・最速で実装！サーバーレスエキスパートが、CognitoによるAWSの認可実装を解説します😎](https://www.ragate.co.jp/blog/articles/6254)
- [AWS cognito とVue.js 3でamplifyを使わないで、EmailでOTP認証させる方法](https://qiita.com/prg_mt/items/fb77a6ce9334fea82605)
- [Cognito を使って認証機能付きのサーバーレスな API を構築する](https://qiita.com/tomoeine/items/74315b084f204c85e22b)
- [AWS AmplifyUI＋Vueでユーザー認証してみる（後編）。](https://qiita.com/silverbox/items/7eaf4b8a2e6c93e2548e)
- [AWS Cognito + Amplify + Not Hosted UI の実装例と注意点](https://qiita.com/kakikukeko09/items/0a640931ed2ee2b89d4b)

### TypeScript
- [TypeScript のメモ置き場](https://qiita.com/toshihirock/items/4ffb65559e70feaae4e9)
- [TypeScript+VueにVuexを導入した際のインクリメントエラーについて](https://qiita.com/shava2C/items/3745a4af034f17082496)
- [【TypeScript】初心にかえってTypeScriptを理解する。](https://qiita.com/crml1206/items/1ffe928339950c4938a4)

### セッションストレージとトークンに関して
- [amazon-cognito-identity-jsがセッション情報をsessionStorageに保存できるようになった](https://qiita.com/kyane/items/e3dc8d4d22236fb2a4c4)
- [amazon-cognito-identity-js で sessionStorageをつかう](https://qiita.com/ume1126/items/50642b8d3c2ed2fbf6fd)
- [AWS Amplifyのログイントークン保存先をsessionStorageに変更する](https://qiita.com/daikiojm/items/7e7ed3d20991718c152d)
- [【個人開発調査】AWS cognitoを使う際のセッション情報管理方法](https://qiita.com/kenogi/items/f87faa634fd76bfdef4b)
- [Local Storageに保管されるCognitoトークンへのセキュリティ対策](https://qiita.com/asw_hoggge/items/cac11b0d9b96c2bfc222)