フロントエンドエンジニアに贈る、爆速Amplifyのすゝめ

この記事は [ハンズラボ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/handslab) 22日目の記事です。

![amplify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408585/b41a1c0e-efbb-a92b-46d7-80b1c2067519.png)
バックエンドさんがAWS cognitoの環境構築をしてくれたなら、
我々フロントエンドは10分でログイン/ログアウトを実装できます。そう、Amplifyならね。

というわけで、この記事では**既存AWSリソースがある**場合の
・ログイン/ログアウト
・REST API呼び出し
・S3読み書き
を、爆速実装する方法を紹介します。

Amplify使い始めて2年が経とうとしていますが、社内にあまり浸透していないので
「これを読めばだいたい使いこなせるようになるぞ！」という記事を目指して書けたらと思います。
簡単さも伝わればいいな。

良い子のみんなは気づいたと思いますがVue.jsでやります（が、Vue.js以外でも応用できると思います）。

- この記事を読む必要がない人
    - [公式ドキュメント](https://docs.amplify.aws/lib/auth/start/q/platform/js/)を読んで自力で実装できる人


## VueCLIを使うよ
言い忘れましたがVue.jsをそこそこ触っている人に向けて書きますので、細かい説明は省きます。

VueCLIで作成したプロジェクトでAmplifyを使います。
バージョンはVueは2系、CLIは3〜4で動くと思います。

```code:bash
$ vue create sample-project
```


## ログイン・ログアウトを実装するよ

カンタン3ステップ
1. VueプロジェクトにAmplifyをインストール
2. 設定
3. ログイン・ログアウト処理のコーディング

### 1. VueプロジェクトにAmplifyをインストール
- プロジェクトフォルダで以下コマンドを実行

```code:bash
$ npm install aws-amplify
または
$ yarn add aws-amplify
```

### 2. 設定
- envファイルに既存cognitoリソースの情報を記載

```code:.env
VUE_APP_REGION='ap-northeast-1' // 東京リージョンの場合
VUE_APP_USER_POOL_ID='ユーザープールID'
VUE_APP_USER_POOL_WEB_CLIENT_ID='ウェブクライアントID'
VUE_APP_IDENTITY_POOL_ID='フェデレーションアイデンティティID'
```

フェデレーションアイデンティティIDはIDプールを使用している場合のみ必要です。

<details><summary>（参考までに）既存cognitoリソースの情報を調べる方法</summary><div>

- ユーザープールID
    - ユーザープール > 全般設定 > プールID
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408585/eac8de38-0932-7cad-c45e-1c875b3c9ae5.jpeg)

- ウェブクライアントID
    - ユーザープール > アプリクライアント > アプリクライアントID
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408585/de4b3a52-6595-23c1-adff-73c3b8775ae0.jpeg)

- フェデレーションアイデンティティID
    - フェデレーティッドアイデンティティ > サンプルコード > AWS認証情報の取得
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408585/f52ff797-ca8e-e851-20ea-e7e1b6a331d6.jpeg)


</div></details>

- `/src` 下のどこかに構成ファイルを配置

```code:bash
$ touch aws-exports.js
```

```aws-exports.js
import Amplify from 'aws-amplify'

Amplify.configure({
  Auth: {
    // リージョン
    region: process.env.VUE_APP_REGION,
    // ユーザープールのID
    userPoolId: process.env.VUE_APP_USER_POOL_ID,
    // ユーザープールのウェブクライアントID
    userPoolWebClientId: process.env.VUE_APP_USER_POOL_WEB_CLIENT_ID,
    // フェデレーションアイデンティティのID
    identityPoolId: process.env.VUE_APP_IDENTITY_POOL_ID,
    // AWSリソースにアクセスする前にユーザー認証を行うかどうか
    mandatorySignIn: true
  },
})

```

- main.jsで構成ファイルをインポート&ロード

```main.js
import Amplify, { Auth } from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
```

### 3. ログイン・ログアウト処理のコーディング
公式ドキュメント：[Sign up, Sign in & Sign out](https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js/)

#### 3-1. ログイン

- サンプルで簡単なログイン画面を作ります

```App.vue
<template>
  <div>
    <input v-model="id" placeholder="ID"/>
    <input v-model="password" placeholder="PASSWORD"/>
    <button @click="login()">login</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      id: "",
      password: "",
    };
  },
  methods: {
    login() {}
  },
}
</script>
```

- Amplifyの認証ライブラリをインポートします

```App.vue
<script>
import { Auth } from 'aws-amplify';

export default {...
```

- ログインボタンクリック時のイベントでsignIn関数を使います

第一引数にユーザーid、第二引数にパスワードを渡してログインします。
ユーザーidは、メールアドレスだったりユーザーネームだったりcognito側の設定に合わせます。

```App.vue
<script>
...
  methods: {
    async login() {
      try {
        const user = await Auth.signIn(this.id, this.password)
        console.log(user);
      } catch (error) {
        console.log('error signing in', error);
      }
    }
  }
```

コンソールログにユーザー情報が表示されていればログイン完了です！（ちなみにレスポンスとしてユーザー情報に何を載せるかはcognito側の設定で変えられます）
エラーだった場合、エラーコードを確認すると原因がわかります。

```App.vue
<script>
...
} catch (error) {
  if(error.code === 'NotAuthorizedException') {
    alert('パスワードが誤っています');
  } else if(error.code === 'UserNotFoundException') {
    alert('ユーザーIDは存在しません');
  } else {
    alert('ログインに失敗しました')
  }
}
```

エラーコード一覧が公式ドキュメントで見つかりませんでした…orz
こちらの記事が参考になると思います：[Cognito のエラーコードまとめ](https://qiita.com/kugyo/items/b3c1d706a85d5c772670)

#### 3-2. ログアウト

- ログアウトボタンクリック時のイベントでsignOut関数を使います

```
Auth.signOut()
```

#### 3-3. （おまけ）よく使う認証系関数

**currentAuthenticatedUser**

```
Auth.currentAuthenticatedUser()
```

認証情報を返します。
認証されていない・セッション期限切れの場合、エラーを返します。
*→ログイン画面で認証済みかどうか確認し、認証済みだったらログイン処理を飛ばしてトップ画面にリダイレクトさせる、ということができる*

**changePassword**

```
Auth.changePassword(user, 'oldPassword', 'newPassword');
```

パスワード変更用の関数です。
第一引数の`user`は、前述した`currentAuthenticatedUser`関数で取得した認証情報になります。

他にもカスタムオーソライザーを使った二段階認証なども簡単に実装できるぞ！
https://docs.amplify.aws/lib/auth/switch-auth/q/platform/js/#customauth-flow

## REST API呼び出しを実装するよ

公式ドキュメント：[API(REST)](https://docs.amplify.aws/lib/restapi/getting-started/q/platform/js/#manual-setup-import-existing-rest-api)

前項で認証したcognitoユーザーが呼び出すという前提でご紹介します。

1. 設定
2. API呼び出し処理のコーディング

### 1. 設定

- さっき作ったaws-exports.jsを編集します

```aws-exports.js
import Amplify from 'aws-amplify'

Amplify.configure({
  Auth: {
    ...
  },
  API: {
    endpoints: [
      {
        name: process.env.VUE_APP_API_NAME,
        endpoint: process.env.VUE_APP_API_ENDPOINT
      }
    ]
  }
})
```

API名とエンドポイントのURLをenvファイルに追加しておいてください。
API名はなんでもいいです。あとでAPI実行時に使います。

### 2. API呼び出し処理のコーディング

- 設定のインポート

```
import { Auth, API } from 'aws-amplify'
const API_NAME = process.env.VUE_APP_API_NAME
```

- getしてみる

```
const token = (Auth.currentSession()).getIdToken().getJwtToken()
const response = API
  .get(API_NAME, `/users`, {
    headers: {Authorization: token}
  })
```

.get()の第一引数にAPI名を入れることで、おそらくエンドポイントを参照してます。
第二引数はエンドポイントより後のパス、
第三引数のヘッダーに`(Auth.currentSession()).getIdToken().getJwtToken()`で取得したtokenを入れます。

以上でデータの受け取りができました。**ね？簡単でしょ？？**
簡単さが伝われば良いので今回はここまでにしておきます。updateやdeleteの処理にも興味があれば[公式ドキュメント](https://docs.amplify.aws/lib/restapi/update/q/platform/js/)を読んでみてください。
また、APIの作りによっても実装方法が変わる可能性がありますので、バックエンドの人と相談してみてください。

もし`Access to XMLHttpRequest at 'https://...' has been blocked by CORS policy` というエラーメッセージが出たら、ユーザーにAPIを叩く権限がない可能性が高いです。

## S3読み書きを実装するよ

公式ドキュメント：[Storage - Using a Custom Plugin](https://docs.amplify.aws/lib/storage/getting-started/q/platform/js/#using-a-custom-plugin)

前々項で認証したcognitoユーザーが読み書きするという前提でご紹介します。
classとかいう苦手概念が出てくるのであまり説明できませんが、コピペでできるように書きます。

1. S3アップロード処理のコーディング
2. 画面からアップロード
3. S3ダウンロード処理のコーディング
4. 画面からダウンロード

### 1. S3アップロード処理のコーディング

[ドキュメント：Upload files](https://docs.amplify.aws/lib/storage/upload/q/platform/js/)

- 別ファイルに書きましょう。`/src` 下のどこかにファイルを配置

```code:bash
$ touch storage-service.js
```

- 最初にStorageライブラリをインポートします

```storage-service.js
import { Storage } from 'aws-amplify'
```

- classを作成し、中に処理を書きます

ファイルを特定のバケットにアップロードする処理を書いてみます。

```storage-service.js
export default class StorageService {
  constructor() {}
  static putFile(file_name, file) {
    Storage.configure({
      AWSS3: {
        bucket: process.env.VUE_APP_FILE_PUT_BUCKET,  // アップロード先のバケットを指定
        region: process.env.VUE_APP_REGION,           // バケットのあるリージョンを指定
      },
      level: 'public'                    // バケットのアクセスレベル。publicはデフォルト
    })
    return (
      Storage.put(file_name, file, {     // 第一引数：S3に置く時のオブジェクト名 第二引数：置くファイル
        progressCallback(progress) {
          console.log(`Uploaded: ${progress.loaded}/${progress.total}`) // opt.アップロード進行度
        },
      })
        .then (result => console.log(result))
        .catch(err => console.log(err))
    )
  }
}
```

`Storage.configure`で操作したいバケットの情報を設定します。
`Storage.put`でファイルをアップロードしています。

### 2. 画面からアップロード

- inputで`file`をv-modelし、ボタンなどのトリガーでファイルをアップロードします

```App.vue
<script>
import StorageService from '@/storage-service.js'

export default {
  data() {
    return {
      file: null,
    }
  },
  methods: {
    upload() {
      StorageService.putFile(this.file.name, this.file) // 第一引数にファイル名、第二引数にファイル
    }
  },
}
</script>
```

まず、さきほど作ったclassをインポートします。
StorageService.putFileを呼び、アップロード処理を実行します。
指定したバケットにファイルが配置されていれば成功です。


### 3. S3ダウンロード処理のコーディング

次にダウンロードも書いていきます。
[ドキュメント：Download files](https://docs.amplify.aws/lib/storage/download/q/platform/js/)

- 先ほど作ったclassに追加していきます

```storage-service.js
export default class StorageService {
  constructor() {}
  static putFile(file_name, file) {
    ...
  },

  static getFile(file_name) {
    Storage.configure({
      AWSS3: {
        bucket: process.env.VUE_APP_FILE_GET_BUCKET,  // アップロード先のバケットを指定
        region: process.env.VUE_APP_REGION,           // バケットのあるリージョンを指定
      },
      level: 'public'                    // バケットのアクセスレベル。publicはデフォルト
    })
    return (
      Storage.get(file_name)             // 第一引数：ダウンロードしたいオブジェクトの名前
        .then ((result) => { return result })
        .catch(err => console.log(err))
    )
  }
}
```
アップロードの時と同じくバケット情報を設定後、`Storage.get`を使います。
レスポンスはデフォルトで署名付きURLになります。

### 4. 画面からダウンロード

```App.vue
<script>
import StorageService from '@/storage-service.js'

export default {
  methods: {
    download() {
      location.href = StorageService.getFile('ファイル名')
    }
  },
}
</script>
```

classをインポートし、StorageService.getFileで署名付きURLを取得して画面からダウンロードさせます。
署名付きURLは期限があるので、ダウンロードボタンが押されるたびに取得するのがいいかと思います。

ダウンロードできない場合、以下のことを確認してみてください。（私がハマったポイントです）

- ファイル名の文字コードがバケット上のファイルと同じか？
- バケット上のファイルのメタデータ：Content-Typeが`binary/octet-stream`になっているか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408585/ccdf030f-7842-1662-83c0-9f7bb4535dd3.png)

また、APIと同じく`Access to XMLHttpRequest at 'https://...' has been blocked by CORS policy` というエラーメッセージが出たら、ユーザーにAPIを叩く権限がない可能性が高いです。

## まとめ
爆速実装できましたでしょうか？
Amplifyの公式ドキュメント、最初は英語が嫌でウワーーーーッとなっていましたが
chromeの日本語翻訳でも十分読めるし、わりと読みやすく書かれているので一番参考になりました。

読んでいただきありがとうございました！
明日の記事もお楽しみに:xmas-tree: