こんにちは、広野です。
本記事は、以下の続編記事です。
前回記事では、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> ); }
そのため、以下のロジックを考えました。
以下の順で処理を実行します。
Amplify.configure は App.jsx などアプリ内のルートに近い位置で実行することが推奨されています。実行する位置にもよるのですが、一度実行するとアプリ全体でその設定が有効になります。そのため、2回実行する例を見たことが無かったのですが、今回実行してみたらうまくいきました。ただし、初回実行時の設定を 2 回目の実行時にも含めておかないと、初回で実行した設定が上書きされてしまうようです。(設定を追記してくれる仕様ではない)
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 でアクセスしたいバケットの設定をしている /* 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 グループ情報の取得
以下の考えに従い、コーディングします。
ログイン成功以降のコンポーネント内で、例として以下のコードを書くことで 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 を表示する画面のコードを修正します。
前段で 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;
以上です!
まとめ
いかがでしたでしょうか。
本記事では、前編で紹介した実装を前提に、React 側のコードを紹介しました。今現在公開されている情報の中で、なんとか工夫してやりたいことを実現してみました。Storage Browser for Amazon S3 の今後の機能拡張やドキュメント充実を引き続きウォッチしつつ、よりよい設計を考えたいと思いました。
本記事が皆様のお役に立てれば幸いです。