AWS AmplifyのQuick Startをやってみた

# はじめに
こんにちは。hoheです。
本日9月6日はANGEL Dojoの中間発表の日で、ANGEL Dojoの活動も残るところ約1ヶ月となります。ANGEL Dojoが始まってから時間の経過が早いと常々感じておりますが、良い成果を出せるように最後まで頑張っていきたいと思います。

本記事はANGEL Calendar 6日目の投稿で、「AWS AmplifyのQuick Startをやってみた」となります。読んでいただけますと幸いです。

また、他のANGEL Dojoに参加されている方の記事は以下からご確認いただけます。

https://qiita.com/organizations/2024-angel-dojo

# AWS Amplifyって何？
皆さんはアプリ開発を普段からしていますでしょうか？私自身は今までの業務ではインフラ分野を担当しており、アプリについてはJavaScriptを少し触った程度の知識しかありませんでした。そんな中で今はANGEL Dojoに参加して四苦八苦しながら開発を進めているところとなります。特にアプリケーションのデプロイには**AWS Amplify**を使用しており、今回は自身の理解を深めるためにAmplifyのQuick Startを実践してみました。


### AWS Amplifyとは
AWS Amplify は Web フロントエンド、モバイルアプリの開発を加速させるために作られたプラットフォームです。Amplify CLIを使用してコマンドラインでの対話的なバックエンドの設定を実施することができ、バックエンドの開発の手間を減らして、フロントエンドの開発に集中することができます。今年GAされたAmplify Gen2からは、Gitリモートリポジトリへのプッシュ時に自動でビルド・デプロイされるようになり、サンドボックス環境も利用できるようになりました。

https://aws.amazon.com/jp/amplify/

# AmplifyのQuick Startを試してみる
Amplifyの公式ドキュメントにてQuick Startが用意されていたため、今回こちらを試してみました。こちらのチュートリアルでは、サンプルのアプリケーションコードが用意されており、はじめに簡単なTodoアプリをAmplifyでデプロイし、最終的にはユーザ個別の認証を実装して、Todoをユーザごとに管理できるようにします。

https://docs.amplify.aws/react/start/quickstart/

## Amplifyでのアプリケーションのデプロイ

アプリケーションをデプロイするまでの流れは以下となります。

1. ソースコードをGitリポジトリに配置します。(今回はGitHubを使用しました。)
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/84f55ada-32b3-7284-6617-06fc35ca4fb9.png" width=100%>

2. Amplifyのコンソールで対象のGitリポジトリ・ブランチを選択します。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/7081e715-e200-facb-8ce5-90cfc54204c7.png" width=100%>

3. アプリケーションのビルド設定・デプロイに必要な権限を設定します。 
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/9a1e56a8-0793-8f17-deb4-a040ecb4e5f7.png" width=100%>

4. 内容を確認し、デプロイを実行します。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/b348a7a7-acfc-9829-30ae-8fc28c92c24d.png" width=100%>

5. デプロイ完了後、URLにアクセスするとデプロイされたTodoアプリが動いていることが確認できました。(アイテムの追加もできます。)
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/a451fb6c-39f8-5bad-6f08-0e6539eceafc.png" width=100%>
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/af999e7e-cc9a-343c-f77c-4ea111f9d377.png" width=100%>

## Todoアプリへの削除機能の追加
最初のTodoアプリではアイテムの追加しかできませんでしたが、次のチュートリアルではタスクの削除機能を実装します。

1. Amplifyのコンソールからamplify_outputs.jsonをダウンロードします。
2. ローカルにGitリポジトリをクローンします。 
    ```
    $ git clone git@github.com:{UserName}/amplify-vite-react-template.git
    $ cd amplify-vite-react-template && npm install
    ```
3. `1.`でダウンロードしたamplify_outputs.jsonをローカルリポジトリのルートディレクトリに配置した後、src/App.tsxに移動してアイテムの削除機能を実装します。
    ```diff_typescript:src/App.tsx
    function App() {
      // ...
    + function deleteTodo(id: string) {
    +   client.models.Todo.delete({ id })
    + }
    
      return (
        <main>
          <h1>My todos</h1>
          <button onClick={createTodo}>+ new</button>
          <ul>
           {todos.map(todo => <li
    +                 onClick={() => deleteTodo(todo.id)}
              key={todo.id}>
              {todo.content}
            </li>)}
          </ul>
          <div>
            🥳 App successfully hosted. Try creating a new todo.
            <br />
            <a href="https://docs.amplify.aws/react/start/quickstart/">Review next step of this tutorial.</a>
          </div>
        </main>
      )
    }
    ```
4. ローカル開発サーバーを起動して`http://localhost:5173/`にアクセスします。
    ```
    $ npm run dev

    > amplify-vite-react-template@0.0.0 dev
    > vite
    
    
      VITE v5.4.2  ready in 683 ms

      ➜  Local:   http://localhost:5173/
      ➜  Network: use --host to expose
      ➜  press h + enter to show help
    ```
5. アプリケーションにて既存のアイテムをクリックすると、リストから対象のアイテムが削除されており、削除機能が正しく動作していることが確認できました。（画像は「Item2」を削除した後）
   ![スクリーンショット 2024-09-04 23.07.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/65ea5c31-accc-5f9b-2d95-54699f6dfa7f.png)

## ログインUIの実装
ここまででTodoアイテムの追加・削除を行えるようになり、次のチュートリアルではログインUIの実装を行いました。

1. src/App.tsxファイルで、認証機能を追記します。
    ```diff_typescript:src/App.tsx
    
    + import { Authenticator } from '@aws-amplify/ui-react'
    + import '@aws-amplify/ui-react/styles.css'
      function App() {
      // ...
        
        return (
    +     <Authenticator>
    +       {({ signOut }) => (
              <main>
              // ...
         
    +           <button onClick={signOut}>Sign out</button>
              </main>
    +       )}
    +     </Authenticator>
        )
      }
    ```
2. ローカル開発サーバーを起動し、`http://localhost:5173/`にアクセスすると、開発サーバーにログイン画面が実装されていることが確認できました。
![スクリーンショット 2024-09-04 23.47.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/f496484d-12fd-cd32-323f-336db514d486.png)


3. ローカルでの変更内容をリモートリポジトリに反映します。
    ```
    $ git add .
    $ git commit -m "added authenticator"
    $ git push origin main
    ```
4. リモートリポジトリにプッシュ後、Amplify上でビルド・デプロイの実行が開始されます。デプロイ完了後にアプリケーションのURLにアクセスすると、`2.`で確認したものと同じログイン画面が確認できました。

## AWS認証情報の設定
ここからはバックエンドを更新して、ユーザーごとの承認ルールを実装し、各ユーザーが自分のToDoにのみアクセスできるようにしていきます。最初にバックエンドの更新のためには、ローカルマシンからバックエンドの更新を展開するためのAWS認証情報が必要になるため、その設定を行っていきます。

1. IAM Identity CenterをAWS Organizationsで有効化します。
![スクリーンショット 2024-09-05 1.15.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/19d696db-43e6-d8e2-d447-5df2e5b7f290.png)
![スクリーンショット 2024-09-05 1.15.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/f5a1096f-6f6d-0bbc-92ff-8315cefab76d.png)

2. CloudShellを開き、以下のコマンドを実行し、AWS アカウントに関連付けるメールアドレスを入力します。
    ```
    $ read -p "Enter email address: " user_email
    $ Enter email address: <your-email-address>
    ```
3. 次に以下のコマンドを実行します。(貼り付け時に複数行コードの貼り付け確認画面が表示されます。)
    ```
    $ response=$(aws sso-admin list-instances)
    ssoId=$(echo $response | jq '.Instances[0].IdentityStoreId' -r)
    ssoArn=$(echo $response | jq '.Instances[0].InstanceArn' -r)
    email_json=$(jq -n --arg email "$user_email" '{"Type":"Work","Value":$email}')
    response=$(aws identitystore create-user --identity-store-id $ssoId --user-name amplify-admin --display-name 'Amplify Admin' --name Formatted=string,FamilyName=Admin,GivenName=Amplify --emails "$email_json")
    userId=$(echo $response | jq '.UserId' -r)
    response=$(aws sso-admin create-permission-set --name amplify-policy --instance-arn=$ssoArn --session-duration PT12H)
    permissionSetArn=$(echo $response | jq '.PermissionSet.PermissionSetArn' -r)
    aws sso-admin attach-managed-policy-to-permission-set --instance-arn $ssoArn --permission-set-arn $permissionSetArn --managed-policy-arn arn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccess
    accountId=$(aws sts get-caller-identity | jq '.Account' -r)
    aws sso-admin create-account-assignment --instance-arn $ssoArn --target-id $accountId --target-type AWS_ACCOUNT --permission-set-arn $permissionSetArn --principal-type USER --principal-id $userId
    ```
4. 以下のコマンドを実行して、上記コマンドが機能したことを確認します。
    ```
    $ printf "\n\nStart session url: https://$ssoId.awsapps.com/start\nRegion: $AWS_REGION\nUsername: amplify-admin\n\n"

    Start session url: https://d-XXXXXXXXXX.awsapps.com/start
    Region: <your-region>
    Username: amplify-admin
    ```
5. IAM Identity Centerのコンソール上で「amplify-admin」ユーザを選択し、[パスワードをリセット>パスワードをリセットする手順を記載したEメールをユーザーに送信]を実行します。
![スクリーンショット 2024-09-05 1.46.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/14636a5b-d0aa-c64c-3375-4ff8bc50b55b.png)

6. 先ほど送信されたEメールのリンクをクリックし、パスワードを設定します。
![スクリーンショット 2024-09-05 1.51.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/0945b042-e410-b056-a8b9-4ea883112777.png)

7. ローカルのターミナルでAWSプロファイルを設定します。(設定値は`4.`で表示されたものです。)
    ```
    $ aws --version
    $ aws configure sso
    The only AWS account available to you is: <your-aws-account-id>
    Using the account ID <your-aws-account-id>
    The only role available to you is: amplify-policy
    Using the role name "amplify-policy"
    CLI default client Region [us-east-1]: <your-region>
    CLI default output format [None]:
    ```
8. ここまで入力するとブラウザが自動で開き、サインインとMFA設定を要求されます。
![スクリーンショット 2024-09-05 2.02.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/60be2b9b-3c88-7448-e9b4-23c1494c4f08.png)
![スクリーンショット 2024-09-05 2.12.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/1b845138-6944-4e2f-03fd-2ca279ecebaf.png)

9. ターミナルに戻って続きを入力します。(CLI profile nameにはdefaultを設定しました。)
    ```
    he only AWS account available to you is: <your-aws-account-id>
    Using the account ID <your-aws-account-id>
    The only role available to you is: amplify-policy
    Using the role name "amplify-policy"
    CLI default client Region [us-east-1]: <your-region>
    CLI default output format [None]:
    CLI profile name [amplify-policy-<your-aws-account-id>]: default
    To use this profile, specify the profile name using --profile, as shown:
    
    aws s3 ls --profile default
    ```

10. 以下のコマンドを入力してSSOプロファイルが表示されることを確認します。
    ```
    $ cat ~/.aws/config
    ```

## クラウドサンドボックスの展開
Amplify Gen2から追加された機能として、チーム内の各開発者ごとに本番環境とは分離して個別のバックエンド環境を提供するクラウドサンドボックスを利用することができます。

`$ npm run dev`を実行中のターミナルとは別に、新規のターミナルを起動後、ローカルにクローンしたアプリケーションのフォルダに移動し、以下のコマンドを実行してクラウドサンドボックスを展開します。
```
// プロファイル名がdefaultの場合
$ npx ampx sandbox
// プロファイル名がdefault以外の場合
$ npx ampx sandbox --profile <profile-name>
```
認証情報が設定されていないと、以下のエラーが発生してサンドボックスが展開できませんでした。
```
InvalidCredentialError: Failed to load default AWS credentials
```

## ユーザーごとの認証の実装
最後にユーザごとの認証を実装し、Todoアプリをユーザごとに分離します。

1. 以下のファイルを変更し、ユーザごとの認証を実装します。
    ```diff_typescript:amplify/data/resource.ts
    import { type ClientSchema, a, defineData } from "@aws-amplify/backend";

    const schema = a.schema({
      Todo: a
        .model({
          content: a.string(),
      
        })
    +    .authorization(allow => [allow.owner()]),
    -    .authorization((allow) => [allow.publicApiKey()]),
    });

    export type Schema = ClientSchema<typeof schema>;

    export const data = defineData({
      schema,
      authorizationModes: {
    +    defaultAuthorizationMode: 'userPool',
    -    defaultAuthorizationMode: "apiKey",
    -    // API Key is used for a.allow.public() rules
    -    apiKeyAuthorizationMode: {
    -      expiresInDays: 30,
    -    },
      },
    });
    ```
    ```diff_typescript:src/App.tsx
    // ... imports

    function App() {
      // ...
      return (
        <Authenticator>
    +      {({ signOut, user }) => (
    -      {({ signOut }) => (
            <main>
    +        <h1>{user?.signInDetails?.loginId}'s todos</h1>
    -        <h1>My todos</h1>
              {/* ... rest of the UI */}
            </main>
          )}
        </Authenticator>
      )
    }
    ```

2. クラウドサンドボックス上で確認するとユーザごとにTodoリストが作れるようになっていることが確認できました。
![スクリーンショット 2024-09-05 17.13.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3875356/00bc7bbf-1279-e257-4a95-e46f44e224f1.png)


    
3. 最後に本番環境にアプリをデプロイしました。クラウドサンドボックス上で作成したTodoやユーザは本番環境には引き継がれず、分離されていることも確認できました。


# さいごに
今回、AmplifyのQuick Startを行ってみました。認証部分の設定は少し慣れが必要だと感じましたが、一度設定してしまえば、以降はかなり簡単に継続したアプリケーションのビルド・デプロイができることを実感できました。こちらのチュートリアルは、AWSアカウントとGitアカウント(手動デプロイする場合はGitアカウントは不要)があれば試すことができるので、興味があればぜひやってみてください。また、今回はフロントエンドのみでしたので、Amplify CLIを使用したバックエンド周りも今後触ってみたいと思います。

# 参考
- [AWS Amplify（アプリケーションの構築とデプロイ）](https://aws.amazon.com/jp/amplify/)
- [AWS Black Belt Online Seminar - AWS Amplify](https://pages.awscloud.com/rs/112-TZM-766/images/20200520_AWSBlackBelt_Amplify.pdf)
- [Amplify Docs](https://docs.amplify.aws/react/start/quickstart/)