【実践・爆速マスター】Amplify Gen2で始める！認証の壁を突破せよ

# はじめに
はじめまして！Amplify大好き@makishy（きしだ）です。煽りタイトルですみません。
AWS Amplify Gen2が登場し、TypeScriptとAWS CDKを活用したコードファーストアプローチやバックエンド設定のコード化などにより、従来に比べてより柔軟にサーバーレスでのフロントエンド、バックエンド構築ができるようになりました。
一方で、多機能で柔軟性が高い分、インターネット上の情報はまだ限られており、設定方法や使い方を学ぶのに一定の時間とコストがかかる状況です。
そこで本記事では、少し複雑で実践的な処理を行うユースケースを想定しながら、Amplify Gen2を活用した実装方法やちょっとしたテクニックなどを紹介していきたいと思います。

# ユースケース
## イベント予約システム
イベント予約システムには以下の機能が求められているとします。
- システム利用にはユーザーログインが必要
- イベント企画者は、イベントの基本情報（日時、場所、タイトル、詳細説明）を登録できる
- ユーザーは、登録されたイベント確認できる
- ユーザーは、イベントへの申し込みができる
- イベントの申し込み結果をユーザーにメール通知する

:::note info
補足：実際のシステムでは、内容の更新や参加人数の制限など様々な要件が入りますが、今回は説明を簡単にするため、非常にシンプルな内容にとどめています。
:::

## 必要な機能の洗い出し
上記システムでは次の機能が必要そうです。
- アカウント登録
- ログイン
- イベントの登録
- イベントの参照
- イベントの申し込み
- メール通知

# アーキテクチャ概要
今回のユースケースに従い、図のようなアーキテクチャを考えてみました。

アーキテクチャ全体
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/9c481bdb-5882-775e-574c-e57cc71a053e.png)

機能が多いので、複数記事に分けて説明をしていきたいと思います。
今回は以下の機能を中心に説明します。
- アカウント登録
- ログイン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/800bb3ff-75f4-de6d-00c3-4e90ca3b0734.png)

アカウント登録やログイン機能は作りこむのに非常に面倒な機能ですが、Amplifyを使うと、とても簡単に機能導入することができます。

# 実装手順
## 1.　Amplify Gen2プロジェクトのセットアップ
まず、フロントエンド、バックエンドに必要なパッケージを導入しAmplify Gen2プロジェクトを作成します。
### 1-1.　フロントエンドプロジェクトの作成
今回の記事の本筋ではないですが、後ほどデータ登録などの操作が必要になるので簡単なフロントエンドを作りたいと思います。今回は、最近新しくなったReactRouterを使いたいと思います。
手順は[こちら](https://reactrouter.com/start/framework/installation)の記事を参考にしています。

React Routerプロジェクトを作成します。プロジェクト名は適宜変更してください。
```
npx create-react-router@latest my-react-router-app
```

作成したプロジェクトに移動して、アプリを起動します。

```
cd my-react-router-app
npm i
npm run dev

```

正常に起動するとターミナル上に次のようなメッセージが表示されます
```
> dev
> react-router dev

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
```
ブラウザを起動し、`http://localhost:5173/`にアクセスすると次のような画面が表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/307ef05e-ebcf-0d5a-ce9d-bdf8633278c0.png" width="50%">

### 1-2.　Amplifyプロジェクトの作成
Amplify公式の[こちら](https://docs.amplify.aws/react/start/manual-installation/)の手順に従って構築します。以下の手順ではコマンドからの作成を行いますが、必要がない方はマニュアルでの配置でも問題ありません。
作成したフロントエンドプロジェクトの直下に移動し、以下のコマンドを実行します。

```
npm create amplify@latest
```
ターミナルで以下のように聞かれるので、そのまま実行します。

```
? Where should we create your project? .
```
無事実行が成功すると以下のようにプロジェクト配下にamplifyフォルダおよび雛形となるリソースが作成されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/ac23dd28-5e3c-0bc6-c4fa-66c26d2e2fc6.png" width="50%">

amplify/data/resource.tsファイルを見ると以下のようにテーブルの雛形が実装されていることを確認できます。

```resource.ts
const schema = a.schema({
  Todo: a
    .model({
      content: a.string(),
    })
    .authorization((allow) => [allow.guest()]),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'iam',
  },
});
```

テーブル設計などの詳しい話は後続で説明を行うとして、一旦ここでは各種リソースをデプロイしながら動作を確認してみることにしましょう。

### 1-3.　サンドボックスの実行
ターミナルから以下のコマンドを実行します。amplify cliなどのアカウント設定は[公式の手順](https://docs.amplify.aws/react/start/account-setup/)などに従い、事前に設定しておいてください。

```
npx ampx sandbox
```

無事、実行が成功すると以下のようなメッセージが表示されます。メッセージが確認できた後はsandboxコマンドを停止しておきましょう。（起動状態の場合、resource.tsの変更の都度バックエンドの反映処理が走り意図せず、リソース更新されることがあるため、停止しておいた方が無難でしょう）

```
✨  Total time: 212.4s

[Sandbox] Watching for file changes...
File written: amplify_outputs.json
```


AWSのマネジメントコンソールに移動して、リソースの状態を確認すると以下のようにバックエンドとなるAppSyncやDynamoDBが作成されていることが確認できます。

AppSync
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/96fdd5aa-8508-c67d-dae1-ca563ed8c228.png" width="50%">


DynamoDB
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/e99178ee-4ab0-4505-1b6b-deda3d6e6619.png" width="50%">


## 2.　認証機能の追加
amplify gen2での認証機能は、以下のリソースで定義されます。
`amplify/auth/resource.ts`
コマンドからAmplifyプロジェクトを作成した場合は以下のようなコードが生成されています。

```resource.ts
import { defineAuth } from "@aws-amplify/backend"

/**
 * Define and configure your auth resource
 * @see https://docs.amplify.aws/gen2/build-a-backend/auth
 */
export const auth = defineAuth({
  loginWith: {
    email: true,
  },
})
```
デフォルトでは、認証リソースはサインインにemailが指定されており、メールアドレスとパスワードによるサインインが構成されます。
作成したCognitoユーザープールのサインインは、メールアドレスの他に、電話番号、ユーザー名を指定することもできます。


### ログイン画面をアプリに組み込む
次にログイン画面をアプリに組み込みます。Amplifyでは豊富なUIライブラリが提供されており、「Authenticator」を利用すると非常に簡単にログイン画面を組み込むことができます。

今回は、未ログインの場合、どの画面にアクセスしてもログイン画面を表示させるようにします。

最初に`@aws-amplify/ui-react`ライブラリをインストールします。

```terminal
npm install @aws-amplify/ui-react
```

`app/root.tsx`を開きApp関数を以下のように書き換えます。

```root.tsx
import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';
import '@aws-amplify/ui-react/styles.css';
import { Authenticator } from "@aws-amplify/ui-react";
Amplify.configure(outputs);

...略...

export default function App() {
  return (
    <Authenticator>
      <Outlet />
    </Authenticator>
  )
}

```
OutletはReactRouterやRemixで登場するコンポーネントであり、ネストされたルーティングにおいて子ルートのコンポーネントをレンダリングするためのプレースホルダーです。簡単に説明するとReactのコンポーネントにおけるchildrenのような役割です。
本筋ではありませんが軽く説明しておくと、例えば以下のようなページ構成だったとします。
```
 app/
├── routes/
│   ├── _index.tsx
│   ├── about.tsx
│   ├── concerts._index.tsx
│   ├── concerts.$city.tsx
│   ├── concerts.trending.tsx
│   └── concerts.tsx
└── root.tsx
```
このとき`app/routes/concerts.`で始まるすべてのルートは、`app/routes/concerts.tsx`の子ルートとなり、親ルートである`concerts.tsx`のOutlet内でレンダリングされることになります。

### 動作確認
ログイン画面の実装はたったこれだけです。
では、動作を確認してみましょう。

改めて、ブラウザから`http://localhost:5173/`にアクセスすると次のような画面が表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/38ef7812-b9c6-0b26-e312-c78228dc2203.png" width="70%">

デフォルトでは、サインイン・サインアウトの他、セルフサインアップも導入されています。
[公式記事](https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-up/)にその他さまざまな設定方法が記載されているので参考にしてください。

今回のユースケース（イベント予約）では、ユーザー自身がアカウント登録して利用する想定ですので、この設定で良さそうです。

試しに、「Create Account」タブからユーザーを作成してみましょう。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/54e7e7c5-fe3c-7fd0-83e1-8b8e0374fac9.png" width="50%">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/117a61c1-dd8c-e74f-9117-a45075f56b5a.png" width="50%">

指定したEメールアドレス宛に送信された認証コードを入力して「Confirm」ボタンを押します。
ログイン画面が再度表示されるので、改めてEメールとパスワードを入力し、ログインします。

ログインに成功すると、以下のようにReactRouterの画面が表示されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/307ef05e-ebcf-0d5a-ce9d-bdf8633278c0.png" width="50%">

ここまでで、システムの要件にあった以下の機能が実装できました。
- アカウント登録
- ログイン

ログイン機能は構築しようとすると非常に労力がかかりますが、AmplifyのUIライブラリを使うことで驚くほど簡単に実装ができました。実際のシステム開発において、実践的にログイン画面を構築する場合には、システム独自のデザインやセキュリティポリシーに合わせた認証ルールのカスタマイズなどが必要となりますが、Amplifyではそのあたりのカスタマイズもサポートしているので、別の機会にご紹介したいと思います。

## 3. ログイン画面のカスタマイズ
今回のログイン画面はAmplifyが提供するUIライブラリを使って簡易に導入しています。このログイン画面はちょっとしたカスタマイズも可能です。凝った画面にしないならこのレベルのカスタマイズで十分なケースもあるでしょう。

公式に[記事](https://ui.docs.amplify.aws/react/connected-components/authenticator/customization)があるのでこれを参考にログイン画面をカスタマイズしてみます。

既存の画面が殺風景なので、画面上部に「実践・爆速マスターシリーズ特設サイト」という文字と画面下部にコピーライトを入れてみます。
実装は簡単で、Authenticatorにcomponentsプロパティに対してHeaderやFooterといったJSX要素を与えれば良いようです。


```root.tsx
const components = {
  Header() {
    const { tokens } = useTheme();
    return (
      <View textAlign="center" padding={tokens.space.large}>
        <h1>実践・爆速マスターシリーズ特設サイト</h1>
      </View>
    );
  },
  Footer() {
    const { tokens } = useTheme();

    return (
      <View textAlign="center" padding={tokens.space.large}>
        <Text color={tokens.colors.neutral[80]}>
          &copy; All Rights Reserved
        </Text>
      </View>
    );
  },
}

export default function App() {
  return (
    <Authenticator components={components}>
      <Outlet />
    </Authenticator>
  )
}

```
実装結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/ecdf65b4-2ea1-4daf-b04b-ea64a98dcf91.png)

その他、いろいろとカスタマイズできるようなので試してみてください。

## 4. 認証の仕組み
Amplifyを使うことで簡単にログイン機能が導入できることを理解しましたが、記事のタイトルに「実践」と謳っているので、もう少し踏み込んでアカウント登録や認証の仕組みについても学んでいきましょう。

 ログイン周りの簡単な処理の流れを説明すると下の左側のような図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/997ca88a-0e0a-410a-8e3f-4aed9e86a1f3.png)

ブラウザからログインを行うと、Cognitoに対して認証処理が行われ、正常に認証されると必要な権限の付与されたアクセストークンが発行されます。
その後、必要なデータの取得などでバックエンドのAPIを呼び出す際にこの発行済みのアクセストークを利用して、AppSyncに対してアクセスを行うことになります。今回はAppSyncを利用していますが、HTTPプロトコルを使った類似のRESTサービスであるAPI GatewayやApplication Load Balancerなどの他のサービスを利用した場合も同様の仕組みで動きます。

少し雑ではありますが、ログインしてから何らかのAPIでデータを取得する流れは以下のようなシーケンスとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/88930853-b9a3-43c8-ac2d-85a4be072661.png)


ちなみに、取得したトークンはログイン後どこに保管されているの？というのが気になるところですが、実はブラウザのローカルストレージを覗くと、値が保存されていることがわかります。（ログイン後にChromeの開発者ツールでローカルストレージを確認した様子）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415206/0d75e0e5-f28c-47d9-9f30-47acdfc9213a.png)


ログインした後で試しに、この値を削除すると自動的にログアウト状態になることからもその動作を確認できるでしょう。


# まとめ
Amplifyを活用することで、複雑な認証機能を驚くほど簡単に実装できることがおわかりいただけたかと思います。わずか数行のコードで、セキュアなユーザー認証システムを構築し、アプリケーションに統合することができました。この強力なツールは、開発者が認証の複雑さに悩まされることなく、アプリケーションの核心的な機能に集中できるようサポートします。今後の記事では、イベント登録やデータ管理など、より高度な機能の実装方法について解説したいと思います。

では、Amplifyで快適なサーバーレスライフをお楽しみください。