こんにちは、広野です。
これまでいろいろと React アプリを AWS リソースと連携させるための仕組みをブログ記事にしてきましたが、絶対に使うであろうユースケースを書いていませんでした。以下、順を追って説明したいと思います。
本記事は UI 編です。
前回の記事
こちらをご覧になった上で、本記事にお戻りください。
アーキテクチャ編では、背景やアーキテクチャを説明しています。
環境編では、主に Lambda@Edge 関数や CloudFront による環境づくりの説明をしています。
React アプリ側の実装
以下の Amazon CloudFront にリクエストを投げる React アプリ側のコードを説明します。
リクエストにはパターンがある
リクエストにはいろいろなパターンがあります。
- 画像の取得。<img>タグが例。
- PDF などのファイルへのリンク。<a>タグが例。
- 動画のストリーミング。
- HTTP GET メソッドによるデータの取得。
当然それぞれのコードは異なるのと、いずれもデフォルトでは Authorization ヘッダー埋め込みに対応していないので、ひとつひとつに対してコードをカスタマイズする必要があります。
しかし、とにかくやることはリクエストの Authorization ヘッダーにトークンを埋め込むことです。それさえできればよいのです。
リクエストパターンごとのコード紹介
ということで、リクエストのパターンごとに実際のコード例を紹介します。
共通の設計にしたことが、最新のトークン取得です。AWS Amplify のモジュールを使用して、Amazon Cognito に問い合わせします。古いトークンを使用してしまうことを避けるためリクエスト直前に都度トークンを取得する設計にしています。
画像
<img> タグをカスタマイズします。<AuthImg> というカスタムコンポーネントを作成し、<img> の代わりに使用します。
画像のバイナリデータを取得し、仮の URL が作られ、それを <img> に埋め込む動きをしています。
- カスタムコンポーネント
import { useState, useEffect } from 'react';
import { fetchAuthSession } from 'aws-amplify/auth';
//Cognito認証付きS3へのカスタムimgコンポーネント
const AuthImg = ({ src, alt, className, ...props }) => {
const [imgSrc, setImgSrc] = useState("");
useEffect(() => {
let objectUrl = "";
const fetchImage = async () => {
try {
const { tokens } = await fetchAuthSession();
const idToken = tokens.idToken.toString();
const response = await fetch(src, {
method: 'GET',
headers: {
'Authorization': `Bearer ${idToken}`
}
});
if (!response.ok) throw new Error('Image fetch failed');
const blob = await response.blob();
objectUrl = URL.createObjectURL(blob);
setImgSrc(objectUrl);
} catch (error) {
console.error("Failed to load authenticated image:", error);
}
};
if (src) {
fetchImage();
}
// クリーンアップ関数:コンポーネントが消える時にメモリを解放
return () => {
if (objectUrl) URL.revokeObjectURL(objectUrl);
};
}, [src]);
// 読み込み中は何も出さない、またはプレースホルダーを出す
if (!imgSrc) return <div className={className} style={{ backgroundColor: '#eee' }} />;
return <img src={imgSrc} alt={alt} className={className} {...props} />;
};
- 使用時のコード
<AuthImg className="figureimg" src={"CloudFrontのURL/指定のパス/xxxxx.png"} alt="xxxxx" />
リンク
<a>タグをカスタマイズする、はずなんですが、私は MUI を使用しているので、MUI の Link コンポーネントをカスタマイズしました。<AuthLink> というカスタムコンポーネントを作成し、<Link> の代わりに使用します。たぶん a タグも同様にすれば動くと思います。
リンク先ファイルのバイナリデータを取得し、仮の URL が作られ、それを <Link> に埋め込む動きをしています。別タブで表示する例です。
- カスタムコンポーネント
import { fetchAuthSession } from 'aws-amplify/auth';
import { Link } from '@mui/material';
//Cognito認証付きS3へのカスタムMuiLinkコンポーネント
const AuthLink = ({ href, children, ...props }) => {
const handleAuthClick = async (event) => {
//通常のリンク遷移(認証なしアクセス)を防止
event.preventDefault();
try {
//トークン取得
const { tokens } = await fetchAuthSession();
const idToken = tokens.idToken.toString();
//認証ヘッダー付きでリクエスト
const res = await fetch(href, {
method: 'GET',
headers: {
'Authorization': `Bearer ${idToken}`
}
});
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
//バイナリデータを取得して表示用のURLを生成
const blob = await res.blob();
const objectUrl = URL.createObjectURL(blob);
//別タブで開く
window.open(objectUrl, '_blank');
} catch (error) {
console.error("Authenticated link access failed:", error);
alert("ファイルの取得に失敗しました。");
}
};
return (
<Link {...props} href={href} onClick={handleAuthClick} sx={{ cursor: 'pointer', ...props.sx }}>
{children}
</Link>
);
};
- 使用時のコード
<AuthLink href={"CloudFrontのURL/指定のパス/xxxxx.pdf"} target="_blank" rel="noopener">
表示するテキスト
</AuthLink>
GET メソッド
Amazon S3 に置いた JSON データのテキストファイルを取得するのに使用しました。axios をカスタマイズして getAuthAxios という関数にしました。md ファイルのテキストデータ取得にも使えます。
- カスタム関数
import { fetchAuthSession } from 'aws-amplify/auth';
import axios from 'axios';
//Cognito認証付きS3へのget関数
const getAuthAxios = async () => {
const { tokens } = await fetchAuthSession();
const idToken = tokens.idToken.toString();
return axios.create({
headers: {
Authorization: `Bearer ${idToken}`
}
});
};
- 使用時のコード
const [data, setData] = useState();
const getData = async () => {
const api = await getAuthAxios();
const res = await api.get("CloudFrontのURL/指定のパス/xxxxx.json", { cache: "no-store" });
setData(res.data);
};
ストリーミング動画 (HLS)
HLS 形式のストリーミング動画を認証付きで取得するのに、react-player の config オプションを適切に設定しました。v3.4.0 だと以下のコードで動きます。ネット上の情報だと古いバージョンの情報が多かったので、気を付けてください。
- 使用時のコード
import { fetchAuthSession } from 'aws-amplify/auth';
import ReactPlayer from 'react-player';
<div className="player-wrapper">
<ReactPlayer
src="CloudFrontのURL/指定のパス/xxxxx.m3u8"
className="react-player"
controls={true}
width="100%"
height="100%"
playing={false}
config={{
hls: {
xhrSetup: async function(xhr, url) {
const { tokens } = await fetchAuthSession();
const idToken = tokens.idToken.toString();
xhr.setRequestHeader('Authorization', `Bearer ${idToken}`);
}
}
}}
/>
</div>
react-player の詳細な設定情報は公式ドキュメントをご覧ください。
まとめ
いかがでしたでしょうか?
いちいちリクエストの Authorization ヘッダーにトークンを埋め込まないといけないので苦労しましたが、一度カスタムコンポーネントを作ってしまえば使い回しできるので楽ですね。
本記事が皆様のお役に立てれば幸いです。


