Storage Browser for Amazon S3 でアクセス可能なバケットを Amazon Cognito グループ単位で動的に変更する [後編]

こんにちは、広野です。

本記事は、以下の続編記事です。

前回記事では、Amazon Cognito の実装を中心に説明しましたが、本記事では React 側の設定やコードを説明します。

仕組みの概要 (おさらい)

まず、ベースとなる AWS リソースのアーキテクチャは以下です。

  • Amazon Cognito ユーザーは、グループに所属させる。本記事では、1ユーザーにつき所属するグループは1つのみとする。
  • Amazon Cognito グループに、グループごとにアクセスを許可するリソースを設定した IAM ロールを関連付ける。
  • それにより、アプリで Amazon Cognito の認証を受けたユーザーは、所属する Cognito グループの IAM ロールを割り当てられる。
  • IAM ロールには、そのグループにアクセスを許可する Amazon S3 バケットへのアクセス権限を記述する。
  • アプリ側では、Amazon Cognito の認証を済ませることにより属性情報の1つとして所属する Cognito グループ名を取得できる。
  • Cognito グループ名から、ビジネスロジックに応じて Storage Browser for Amazon S3 でアクセスさせる S3 バケットを動的に設定するアプリコードを書く。

React 側の実装

ベースとなる設定、コードについては、以下の記事をご覧ください。これをカスタマイズしていきます。

やりたいことは、Amplify.configure で設定していた Amazon S3 バケットをユーザーが所属している Cognito グループによって動的に変えたい、でした。しかし、Amplify.configure の中で Amazon Cognito の設定と Amazon S3 バケットの設定を同時に実行しているため、この状態では Cognito グループ情報が取得できていない状態で Amazon S3 バケットの設定をしています

App.jsx 変更前コード

  • App.jsx (必要なところだけ抜粋)
import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react';
import { Amplify } from 'aws-amplify';
import Loggedin from './Loggedin.jsx';
import Notloggedin from './Notloggedin.jsx';

//Cognito, S3 連携設定
Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: import.meta.env.VITE_USERPOOLID,
      userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID,
      identityPoolId: import.meta.env.VITE_IDPOOLID
    }
  },
  Storage: {
    S3: {
      bucket: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
      region: import.meta.env.VITE_REGION,
      buckets: {
        amplifystorage: {
          bucketName: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
          region: import.meta.env.VITE_REGION
        },
        //以下で、Storage Browser でアクセスしたいバケットの設定をしている(固定値)
        kbdatasource: {
          bucketName: import.meta.env.VITE_S3BUCKETNAMEKBDATASRC,
          region: import.meta.env.VITE_REGION,
          paths: {
            "bot1/*": {
              authenticated: ["write", "list", "delete"]
            },
            "bot2/*": {
              authenticated: ["write", "list", "delete"]
            }
          }
        }
      }
    }
  }
});

//ログインチェック
function Authcheck() {
  const { authStatus } = useAuthenticator((context) => [context.authStatus]);
  return (authStatus === "authenticated") ? <Loggedin /> : <Notloggedin />;
}

export default function App() {
  return (
    <Authenticator.Provider>
      <Authcheck />
    </Authenticator.Provider>
  );
}

そのため、以下のロジックを考えました。

Storage Browser を使用する直前に、Cognito グループの情報を取得した状態でもう 1 回 Amplify.configure を実行すればよいのではないか?

以下の順で処理を実行します。

1回目: 従来通り、App.jsx 内で Amplify.configure を実行するが、Storage Browser に関する設定は除く。
1回目の Amplify.configure 実行により、Amazon Cognito にアクセスできる状態になっているため、Cognito グループ情報を取得し、それによってアクセス可能な Amazon S3 バケットを判定する処理を実行する。
2回目: Storage Browser を使用するコンポーネント内で再度 Amplify.configure を実行する。ここに、Storage Browser の設定を含める。

Amplify.configure は App.jsx などアプリ内のルートに近い位置で実行することが推奨されています。実行する位置にもよるのですが、一度実行するとアプリ全体でその設定が有効になります。そのため、2回実行する例を見たことが無かったのですが、今回実行してみたらうまくいきました。ただし、初回実行時の設定を 2 回目の実行時にも含めておかないと、初回で実行した設定が上書きされてしまうようです。(設定を追記してくれる仕様ではない)

App.jsx 変更後コード

以下の考えに従い、修正します。

1回目: 従来通り、App.jsx 内で Amplify.configure を実行するが、Storage Browser に関する設定は除く。
import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react';
import { Amplify } from 'aws-amplify';
import Loggedin from './Loggedin.jsx';
import Notloggedin from './Notloggedin.jsx';

//Cognito, S3 連携設定
Amplify.configure({
  Auth: {
    Cognito: {
      userPoolId: import.meta.env.VITE_USERPOOLID,
      userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID,
      identityPoolId: import.meta.env.VITE_IDPOOLID
    }
  },
  Storage: {
    S3: {
      bucket: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
      region: import.meta.env.VITE_REGION,
      buckets: {
        amplifystorage: {
          bucketName: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
          region: import.meta.env.VITE_REGION
        },
        //以下で、Storage Browser でアクセスしたいバケットの設定をしている
        /* Storage Browser 用の設定を取り除きます。
        kbdatasource: {
          bucketName: import.meta.env.VITE_S3BUCKETNAMEKBDATASRC,
          region: import.meta.env.VITE_REGION,
          paths: {
            "bot1/*": {
              authenticated: ["write", "list", "delete"]
            },
            "bot2/*": {
              authenticated: ["write", "list", "delete"]
            }
          }
        }
        */
      }
    }
  }
});

//ログインチェック
function Authcheck() {
  const { authStatus } = useAuthenticator((context) => [context.authStatus]);
  return (authStatus === "authenticated") ? <Loggedin /> : <Notloggedin />;
}

export default function App() {
  return (
    <Authenticator.Provider>
      <Authcheck />
    </Authenticator.Provider>
  );
}

Cognito グループ情報の取得

以下の考えに従い、コーディングします。

1回目の Amplify.configure 実行により、Amazon Cognito にアクセスできる状態になっているため、Cognito グループ情報を取得し、それによってアクセス可能な Amazon S3 バケットを判定する処理を実行する。

ログイン成功以降のコンポーネント内で、例として以下のコードを書くことで Cognito グループは取得できます。

import { useState, useEffect } from 'react';
import { useAuthenticator } from '@aws-amplify/ui-react';
import { fetchAuthSession } from 'aws-amplify/auth';

//ログイン後の画面
const Loggedin = () => {
  //ステート定義
  const [username, setUsername] = useState();
  const [authToken, setAuthToken] = useState();
  const [groups, setGroups] = useState();
  
  //ユーザ情報取得
  const { user, signOut } = useAuthenticator((context) => [context.user]);
  
  //セッション情報取得関数
  const getSession = async () => {
    const { tokens } = await fetchAuthSession();
    setUsername(tokens?.idToken?.payload.email); //メールアドレスを取得しています。
    setGroups(tokens?.idToken?.payload["cognito:groups"]); //Cognitoグループ名を取得しています。(配列になります)
    setAuthToken(tokens?.idToken?.toString()); //IDトークンを取得しています。
  };
  
  //画面表示時実行
  useEffect(() => {
    getSession();
  }, []);

//以降省略

groups というステートに Cognito グループ名が配列で格納されるので、それをもとに、どの Amazon S3 バケットにアクセス許可するか判定するビジネスロジックは別途必要です。

Storage Browser 周辺のコード

以下の考えに従い、Storage Browser を表示する画面のコードを修正します。

2回目: Storage Browser を使用するコンポーネント内で再度 Amplify.configure を実行する。ここに、Storage Browser の設定を含める。

前段で Cognito グループ情報をもとにアクセス可能とする Amazon S3 バケットが確定している前提で、Amazon S3 バケット名を以下のサンプルコード内ではハードコーディングします。実際には動的に値が変わる想定です。

import { Amplify } from 'aws-amplify';
import '@aws-amplify/ui-react-storage/styles.css';
import { createAmplifyAuthAdapter, createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';

const Ragkb = (props) => {
  //state初期化 2回目の Amplify.configure は1回だけ実行すればよいので実行済みフラグを作成
  const [isAmplifyConfigured, setIsAmplifyConfigured] = useState(false);
  
  //Cognito, S3 連携設定
  const amplifyConfigure = () => {
    Amplify.configure({
      //2回目の Amplify.configure 実行時も Cognito の設定は必要
      Auth: {
        Cognito: {
          userPoolId: import.meta.env.VITE_USERPOOLID,
          userPoolClientId: import.meta.env.VITE_USERPOOLWEBCLIENTID,
          identityPoolId: import.meta.env.VITE_IDPOOLID
        }
      },
      Storage: {
        S3: {
          bucket: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
          region: import.meta.env.VITE_REGION,
          buckets: {
            amplifystorage: {
              bucketName: import.meta.env.VITE_S3BUCKETNAMEAMPLIFYSTG,
              region: import.meta.env.VITE_REGION
            },
            //ここで、1回目の Amplify.configure 実行時には無かった Storage Browser 用の S3 バケットを設定
            kbdatasource: {
              bucketName: 'example-xxx-kbdatasource', //バケット名を入れる、ここが動的に変わる想定
              region: import.meta.env.VITE_REGION,
              //フォルダも同様に動的に変更は可能
              paths: {
                "bot1/*": {
                  authenticated: ["write", "list", "delete"]
                },
                "bot2/*": {
                  authenticated: ["write", "list", "delete"]
                }
              }
            }
          }
        }
      }
    });
    //2回目の Amplify.configure を実行したら、実行済みフラグを true にする
    setIsAmplifyConfigured(true);
  };
  
  //Storage Browser
  const { StorageBrowser } = createStorageBrowser({
    config: createAmplifyAuthAdapter()
  });

  //画面表示時
  useEffect(() => {
    //Amplify.configure 再実行、ただしフラグを確認して1回だけ実行する
    if (!isAmplifyConfigured) {
      amplifyConfigure();
    }
  }, []);

  return (
    <React.Fragment>
      {/* 中略 */}
        {/* 2回目の Amplify.configure が実行済みでないと Storage Browser を表示させない */}
        {(setIsAmplifyConfigured) && <StorageBrowser />}
      {/* 中略 */}
    </React.Fragment>
  );
};

export default Ragkb;

 

ここまで実行できると、、、想定通り Storage Browser を表示することができました!

以上です!

まとめ

いかがでしたでしょうか。

本記事では、前編で紹介した実装を前提に、React 側のコードを紹介しました。今現在公開されている情報の中で、なんとか工夫してやりたいことを実現してみました。Storage Browser for Amazon S3 の今後の機能拡張やドキュメント充実を引き続きウォッチしつつ、よりよい設計を考えたいと思いました。

本記事が皆様のお役に立てれば幸いです。

関連記事

【re:Invent 2024 発表】Storage Browser for Amazon S3 を React アプリに組み込みました
2024/12/1 に GA されました Storage Browser for Amazon S3 を既存 の S3 と React アプリに組み込みました。(AWS Amplify, Next.js 不使用)
Storage Browser for Amazon S3 でダウンロードを無効にする
デフォルトの設定ではセキュリティ的に実用的ではなかったので、少々セキュリティ設定を組み込んでみました。
Storage Browser for Amazon S3 のアクセスログを取得・検索する [AWS CloudTrail 利用]
Storage Browser を使用する上で絶対に必要になる、アクセスログを取得する設定を入れました。
Storage Browser for Amazon S3 でアクセス可能なバケットを Amazon Cognito グループ単位で動的に変更する [前編]
前編では、Amazon Cognito ID プールの実装を中心に説明します。
著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWSは好きですが、それよりもフロントエンド開発の方が好きでして、バックエンド構築を簡単にしてくれたAWSには感謝の気持ちの方が強いです。
取得資格:AWS 認定は13資格、ITサービスマネージャ、ITIL v3 Expert 等
2020 - 2024 Japan AWS Top Engineer 受賞
2022 - 2024 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSアプリケーション開発クラウドソリューション
シェアする
タイトルとURLをコピーしました