Storage Browser for Amazon S3 を Remix アプリに組み込んでみた

本記事は、KDDIアジャイル開発センター（KAG）Advent Calender 2024 17 日目の記事です。

https://qiita.com/advent-calendar/2024/kag

## はじめに
先日リリースされた S3 の新機能 [Storage Browser for Amazon S3](https://aws.amazon.com/jp/about-aws/whats-new/2024/12/storage-browser-amazon-s3/)を試してみました。これは、独自のアプリから直接 S3 データにアクセスできる機能で、Amplify UI の React コンポーネントとして提供されています。[コンポーネントのドキュメント](https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser)や[ブログ](https://aws.amazon.com/jp/blogs/aws/connect-users-to-data-through-your-apps-with-storage-browser-for-amazon-s3/
)を参考に構築方法を確認し、今回はこれを [Remix](https://remix.run/) アプリに組み込んでみます。

本記事では以下のような設定で構築していきます。

- フロントエンドは Remix(SPA Mode) を利用
- 認証処理は Amplify auth を利用
- ブラウズ対象のバケットは Amplify Storage で新規作成

## 環境構築

環境構築を新規で作成します。作成したソース一式は、以下に配置しましたので、必要に応じて確認してください。

https://github.com/ore88ore/remix-storage-browser

### 実行環境

Remix: `2.15.0`
aws-amplify/ui-react: `6.7.1`
Node.js: `20.15.0`
npm: `10.8.2`

### Remix(SPA Mode)

Remix を SPA Mode のテンプレートを使って、プロジェクトを作成します。

```shell
npx create-remix@latest --template remix-run/remix/templates/spa
```

### Amplify Gen 2 をセットアップ

```shell
npm create amplify@latest
```

### Amplify UI React, Storage

```shell
npm install @aws-amplify/ui-react aws-amplify @aws-amplify/ui-react-storage
```

### 起動してみる

以下のコマンドを実行して、localhost:5173 で起動していれば OK

```shell
npm run dev
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/d0886935-f795-a237-5f0c-25e97e60fdcf.png)

## Amplify で必要なリソースを作成

Storage Browser を利用するために必要なリソースを作成していきます。Authentication としては、Amplify auth, AWS IAM Identity Center and S3 Access Grants, Customer managed auth から選択することができます。今回は素早く立ち上げることができそうなので、 **Amplify auth** (認証は Cognito) を使って構築していきます。
また、ブラウズ対象の S3 バケットは、Amplify Storage を利用して、S3 バケットの作成、アクセスするための設定を実施します。

:::note
既存の S3 バケットを利用する場合は、CORS を有効にする必要があります。
詳細は[公式ドキュメント](https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser#bucket-cors
)を確認してください。
:::

### Authentiction

公式ドキュメントにある通り、リソースを設定します。利用環境や制約に応じて設定を変更することができます。

https://docs.amplify.aws/react/build-a-backend/auth/set-up-auth/

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

export const auth = defineAuth({
  loginWith: {
    email: true,
  },
});

```

バックエンドのリソースに作成した、`auth` を追加します。

```typescript:amplify/backend.ts
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";

defineBackend({
  auth,
});
```

### Storage

今回は Amplify で新規で S3 バケットを作成します。バケットの設定はこちらの[ドキュメント](https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser#amplify-auth)を参考に作成します。ここでバケット内のオブジェクトのプリフィックスやキーに応じてアクセス制御の設定を定義します。

https://docs.amplify.aws/react/build-a-backend/storage/set-up-storage/

今回作成するバケットには以下のようなアクセス制御を入れました。
- `public/*`
    - 匿名ユーザー（未認証ユーザー）に読み取り権限を付与
    - 認証ユーザー（誰でも）に読み取り、書き込み、削除権限を付与
- `private/[認証ユーザーのID]/*`
    - 対象の認証ユーザー（作成者）に読み取り、書き込み、削除権限を付与（作成者専用）
    - 作成者以外のユーザーは権限がないので見えない

アクセス制御の詳細については、公式ドキュメントを確認してください。

https://docs.amplify.aws/react/build-a-backend/storage/authorization/

```typescript:amplify/storage/resource.ts
import { defineStorage } from "@aws-amplify/backend";

export const storage = defineStorage({
  name: "remix-storage-browser",
  access: (allow) => ({
    "public/*": [
      allow.guest.to(["read"]),
      allow.authenticated.to(["read", "write", "delete"]),
    ],
    "private/{entity_id}/*": [
      allow.entity("identity").to(["read", "write", "delete"]),
    ],
  }),
});
```

バックエンドのリソースに作成した、`storage` を追加します。

```diff_typescript:amplify/backend.ts
import { defineBackend } from "@aws-amplify/backend";
import { auth } from "./auth/resource";
+ import { storage } from "./storage/resource";

defineBackend({
  auth,
+   storage,
});

```

### リソースを作成

以下のコマンドを実行して、個人の sandbox 環境に定義したリソースを作成します。

```shell
npx ampx sandbox
```

リソースが作成されると、`amplify_outputs.json` が作成されます。ファイルには、作成されたリソースの情報が記述されているので、意図したリソースが作成されているかを確認してみてください。

## Storage Browser コンポーネントを配置

リソースが作成できたら、Storage Browser コンポーネントをページに配置します。
[ドキュメント](https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser#amplify-auth)で提供されているサンプルの通り、設定すれば問題なく表示できることを確認できました。Amplify auth を利用したこともあって、すごく楽ですね。
コンポーネントをカスタマイズしたい場合は、コンポーネントの props を設定することで実施できそうです。カスタマイズの詳細は、公式ドキュメントをご確認ください。

https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser#customization

```typescript:app/routes/authenticated.tsx
import { Amplify } from "aws-amplify";
// npx ampx sandbox コマンドで作成された amplify_outputs.json ファイルをインポート
import config from "../../amplify_outputs.json";

import {
  createAmplifyAuthAdapter,
  createStorageBrowser,
} from "@aws-amplify/ui-react-storage/browser";
import { Authenticator } from "@aws-amplify/ui-react";

// Amplify.configure(config) を呼び出して、Amplify を設定
Amplify.configure(config);

export const { StorageBrowser } = createStorageBrowser({
  // Amplify auth を利用した場合は createAmplifyAuthAdapter() を使用
  config: createAmplifyAuthAdapter(),
});

export default function Index() {
  return (
    // 認証を必須にしたい場合、Authenticator コンポーネントでラップ
    <Authenticator>
      <StorageBrowser />
    </Authenticator>
  );
}
```

## 動作確認

コンポーネントを配置した画面を表示して、動作確認してみます。

### ログインしてから表示

`Authenticator` でラップしたコンポーネントを表示すると、未ログインの場合 Cognito のログインを求められる(Amplify auth の機能)ので、登録したユーザーでログインします。
ログイン後にページを表示すると以下のように、`private/[認証ユーザーのID]/*`, `public/*` フォルダへのアクセス権が、読み取り、書き込みが付与されていることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/fc1d0cc3-954f-3e3f-f247-d752aee298a4.png)

`private/[認証ユーザーのID]/*` フォルダに、ファイルやフォルダをアップロードすることを確認できました。また、検索、オブジェクトの削除、オブジェクトのコピーなどできることを確認できました。一通りの必要な操作が Storage Browser コンポーネントだけでできそうですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/4fda44b3-d0ca-ed34-f525-01ad42e456b2.png)

### 未ログインで表示

次に未ログインの状態（匿名ユーザー）で、表示してみます。
`public/*` フォルダへの読み取りアクセス権のみとなっていることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/e82599b3-32bc-adf6-a1a4-dba9bd59de71.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/d3ffbde1-9db2-d802-7e7f-a8770ccad93a.png)

意図した動きになってそうですね。

## さいごに

Storage Browser を使うことで、簡単に S3 のオブジェクトの操作を Web アプリに組み込めそうでした。自前で実装するよりは遥かに楽に組み込めるので、今後、利用する機会が増えるかもしれないですね。
各種サンプルは、React をベースとしていたので、Remix でも使えるだろうと思い、今回は実際に構築するところまで、試してみました。
Storage Browser を既存の仕組みに組み込む場合は、既存の S3 バケットに組み込むパターンや各種カスタマイズすることが多いかと思うので、引き続き試してみようと思います。

## 参考

https://ui.docs.amplify.aws/react/connected-components/storage/storage-browser

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/storage-browser.html

https://aws.amazon.com/jp/blogs/aws/connect-users-to-data-through-your-apps-with-storage-browser-for-amazon-s3/

https://github.com/ore88ore/remix-storage-browser