AWS AmplifyUI＋Vueでユーザー認証してみる（後編）。

# 前説

[AWS AmplifyUI＋Vueでユーザー認証してみる（前編）。](https://qiita.com/silverbox/items/d3c5c19221d2aa8a83ae)の続き記事になります。
前編では、Amplifyのプロジェクトを新規作成し、ユーザー認証のUIコンポーネントを追加してみる所まで行いました。
今回の記事では、その情報を参考に、既存のVueプロジェクトにユーザー認証用のAmplifyUIコンポーネントを適用する所まで行います。

# AmplifyUIコンポーネント導入に必要な内容確認

まず、前編の記事で、AmplifyUIを追加した事で発生した変化を確認します。

増えたファイルは以下の２つです。こちらはAmplifyプロジェクトとしての情報を保持している様です。
- amplify/backend/auth/myamplifyproject*********/cli-inputs.json
- amplify/backend/backend-config.json

認証関係の情報が追加されたのは以下のファイルです。
- amplify/backend/types/amplify-dependent-resources-ref.d.ts
- amplify/team-provider-info.json

App.vueに以下の部分が追加されました。

```vuejs
<script setup>
  import { Authenticator } from '@aws-amplify/ui-vue';
  import '@aws-amplify/ui-vue/styles.css';
</script>
```


既存プロジェクトはAmplifyのプロジェクトではないので、４ファイルに関してはそのまま追加は出来なさそうです。情報としては認証に使われるcognitoのIDなどが記載されています。まとめると以下の対応になりそうです。

- モジュールのインポート
- 認証用に使われるcognitoの情報設定

これらのキーワードで検索していると、オフィシャルのページにそのままの情報がありました。

https://ui.docs.amplify.aws/vue/getting-started/installation

https://docs.amplify.aws/lib/auth/getting-started/q/platform/js/#option-1-use-pre-built-ui-components


# 実対応

実際に適用していきます。

## Vue3へ移行

上記ページには下記メッセージがあります。Vue2ではGoogleアカウントなどのソーシャルログインとの連携があまり考慮されてない過去のコンポーネントしか使えなさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530376/8551e2ed-e836-cef0-8f2f-8b6dbc21bd56.png)
これはこれで大きな話題ですが、今回の件と本質的には関係無いのでまたの機会にします。
簡単に言うと、Vue3の新規プロジェクトを作成し、使っていたrouterやelement-uiなどモジュールをVue3用に替え、既存ソースを順次移植する方法を採用しています。記事を書いている時点では、初期表示するページだけ移行している状況です。

## モジュールのインポート

公式ページ通りです。

```
yarn add @aws-amplify/ui-vue aws-amplify
```

## 認証用に使われるcognitoの情報設定

Login部分に使用するVueファイルでは以下の記載が必要です。ここが設定する部分です。

```typescript
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
```

前編の記事ではaws-exports.jsというファイルがあり、それはAmplifyが自動的に更新する設定ファイルという事が解っています。それをもとに、Amplify.configureを実行して情報をセットする必要があります。

公式ページや、それを実際に使用している記事がありました。実際に使用されている記事からすると、公式ページの`Scoped configuration`を行っておけば大丈夫そうです。前編の記事で生成されていたのは`Top level configuration`の様です。

https://docs.amplify.aws/lib/client-configuration/configuring-amplify-categories/q/platform/js/#authentication-amazon-cognito

https://www.ragate.co.jp/blog/articles/4438

https://qiita.com/sakusaku_tempura/items/60f8ffed9f81d200bb07#amplify%E8%A8%AD%E5%AE%9A%E7%94%A8%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

```typescript:今回使用したパラメーター
export default {
  Auth: {
    // (required) only for Federated Authentication - Amazon Cognito Identity Pool ID
    identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',

    // (required)- Amazon Cognito Region
    region: 'XX-XXXX-X',

    // (optional) - Amazon Cognito User Pool ID
    userPoolId: 'XX-XXXX-X_abcd1234',

    // (optional) - Amazon Cognito Web Client ID (26-char alphanumeric string, App client secret needs to be disabled)
    userPoolWebClientId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3',
  }
} 
```

# 実際のアプリで使用

CognitoとAmplifyUIの導入が済んだ状態です。実際にアプリで使用していきます。大きく分けて２種類の仕様部分があります。

- ログイン処理
- トークンを使ってAPIアクセス


## 基本情報の取得

実際には、ログイン後に各コンポーネントでログイン情報を使用する必要があります。基本情報は公式ページにありました。

https://ui.docs.amplify.aws/vue/connected-components/authenticator/headless

まず、useAuthenticatorを使ってauthオブジェクトを生成しています。

```typescript:公式ページ引用
import { Authenticator, useAuthenticator } from '@aws-amplify/ui-vue';
const auth = useAuthenticator();
```

その後、`auth.user`でユーザー情報を参照したり、`auth.route`でログイン状況を取得できます。

```Vue:公式ページ引用
  <template v-if="auth.route === 'authenticated'">
    <h1>Hello {{ auth.user?.username }}!</h1>
    <button @click="auth.signOut">Sign out</button>
  </template>
```

## ログイン処理

[サンプル](https://ui.docs.amplify.aws/vue/connected-components/authenticator/headless#access-authenticated-user)を真似て、ログイン成功まではログイン画面のみを表示し、成功したらrouterを表示する形にします。

```vue:App.vue
<template>
  <authenticator>
    <template v-slot="{ signOut }">
      <button @click="signOut">Sign Out</button>
    </template>
  </authenticator>
  <div id="app">
    <div v-if="auth.route === 'authenticated'">
      <router-view/>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-vue";
import { Amplify } from 'aws-amplify';
import "@aws-amplify/ui-vue/styles.css";
import awsconfig from '@/aws-exports';
Amplify.configure(awsconfig);

export default defineComponent({
  setup() {
    const auth = ref(useAuthenticator());
    return {
      auth
    }
  },
  components: {
    Authenticator
  },
});
</script>
```

## トークンを使ってAPIアクセス

今回AmplifyUI導入のアプリでは、APIアクセスで、APIGatewayでCognitoオーサライザーを使用しています。IDトークン内のjwtトークンをHeaderで渡す必要があります。

### ログイン情報の取得

ユーザー情報は[CognitoUserAmplify](
https://ui.docs.amplify.aws/vue/connected-components/authenticator/headless#full-api)クラスで取得出来そうです。

[元ソース](https://github.com/aws-amplify/amplify-ui/blob/f9be291e4b89137d7b521bd3e82b4f96ee4323f6/packages/ui/src/types/authMachine.ts#L113-L115)を見ると`CognitoUser`の継承オブジェクトの様です。`amazon-cognito-identity-js`からimportされています。

必要なプロパティは上記[amazon-cognito-identity-jsがあるリポジトリ](https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/src/CognitoUser.js)を探せばよさそうです。サンプルで使っている`username`もあります。

### トークンの取得

APIアクセスの時に、ヘッダーにjwtトークンを埋める必要があります。今までは、`session.getIdToken().getJwtToken()`という関数を使っていました。

前述の`CognitoUser.js`のリポジトリを見ると、[getSession](https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1366)にcallback関数をセットする様です。
そして、そのcallbackは[最終的](https://github.com/aws-amplify/amplify-js/blob/be53c6187057728a9e2cf24265324fd1f470e5e0/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1479)に(error,session)というパラメーターが渡されるようです。
そこからは[同様](https://github.com/aws-amplify/amplify-js/blob/be53c6187057728a9e2cf24265324fd1f470e5e0/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1499)にgetIdToken().getJwtToken()を使えそうです。この部分で`this.signInUserSession`を使っていますが、リフレッシュ処理などを考慮したsessionを`getSession`関数で取得するべきかと思います。

今回は、以下の様なモジュールを作成しました。

```typescript:api.ts
import axios from 'axios';
import { BalanceView } from '@/common/interfaces';
import masterdata from '@/const/masterdata';
import { useAuthenticator } from '@aws-amplify/ui-vue';
const API_BASE_URL = (process.env.VUE_APP_API_BASE_URL as string);

export default class ApiCalls {
  // 共通して使用する関数
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  callGetApi = async (url: string, config: {[key:string]: any}, callback: any): Promise<any> => {
    const auth = useAuthenticator();
    auth.user.getSession(async (error: Error, session: CognitoUserSession) => {
      if (error != null) {
        throw error;
      }
      const addHeaders = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${session.getIdToken().getJwtToken()}`
      };
      const newHeaders = config['headers'] ? {
        ...config['headers'],
        ...addHeaders
      } : addHeaders;
  
      const newConfig = {
        ...config,
        'headers': newHeaders
      }
      const response = await axios.get(url, newConfig);
      return callback(response);
    });
  };

  // ビジネスロジック用関数での使用例
  getBalanceList = async (tgtDate: string): Promise<BalanceView[]> => {
    return new Promise(resolve => {
      const url = `${API_BASE_URL}/balance?tgt_date=${tgtDate}`;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      this.callGetApi(url, { data: {}}, (response: any) => {
        const wkBalanceList: BalanceView[] = [];
        for (const resdata of response.data) {
          const wkBalance: BalanceView = {
            method_nm: masterdata.getMethodNm(resdata['method_cd']),
            value_fmt: Number(resdata['value']).toLocaleString()
          }
          wkBalanceList.push(wkBalance);
        }
        resolve(wkBalanceList);
      });
    });
  };
}
```

使用する側は以下の様な感じです。

```typescript
const api = new ApiCalls();
// 中略
const balanceList = await api.getBalanceList(tgtToDateStr);
```

# 感想

通常、ログイン画面だけなら結構シンプルですが、ユーザー登録、パスワード変更画面など思いのほか実装が必要な画面があります。そこをパッケージングしてくれるこのAmplifyUIはすごく便利だと思いました。

今回はそのAmplifyUIをAmplifyプロジェクトでない通常のVueプロジェクトで使用してみました。Cognitoオーサライザーに対応しているAPIアクセスも出来ました。

Vue3への移行途中です。今回で技術的課題が無くなりました。これで安心して残りのVue3移行を進める事が出来ます。