Azure Static Web AppからAzure Blob Storageの静的ホスティングで公開されている画像をAzure CDN経由でCORS対応して取得したメモ

## 概要
[前回](https://qiita.com/hibohiboo/items/71d1af715c25707a32fc)はAzure Blob Storageに画像のアップロードを行った。
アップロードした画像を使いたいが、Azure Blob Storageの静的ホスティングで公開すると、Azure Static Web Appsとドメインが別になる。
[同一オリジンポリシー](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy)により、JSでのfetchができなかったり、[キャンバスの汚染](https://developer.mozilla.org/ja/docs/Web/HTML/CORS_enabled_image)の問題があるため、これを解決したい。

[Blob Storageの静的ホスティングはCORSをサポートしていない](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blob-static-website#viewing-content)ため、[AWSでいうCloudFrontにあたるAzure CDN](https://qiita.com/hayao_k/items/906ac1fba9e239e08ae8)を使って解決することとする。

## 静的ホスティング

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/9fd3bc06-9025-d8d0-fd0e-c8c0c77193d0.png)

有効にすると、$webというコンテナが作成される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/5d78a3a6-79ae-9d9f-5b9c-fee0b3ea50eb.png)

ソースもコンテナに対応するように修正し、アップロードを試してみる。

```diff
export const useImageUploaderPageHooks = () => {
-  const containerName = `images`;
+  const containerName = `$web`;
```


アップロードはうまく行った。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/b5a2b90c-9123-1428-19c2-ccfe329a3b13.png)


## アクセスできないことの確認

次に、コードからアップロードしたファイルをダウンロードしようとしてみる。

```ts
  const res = await fetch('https://hoge.z11.web.core.windows.net/fuga.png', {
    mode: 'cors',
  });

```

上記のコードだと、下記のエラーとなる。同一オリジンポリシー違反のため。

```
Access to fetch at 'https://hoge.z11.web.core.windows.net/fuga.png' from origin 'https://piyo.5.azurestaticapps.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```

## CDNの作成

下記のようにストレージアカウントの「セキュリティとネットワーク > Front Door と CDN」を選択。作成する。元のホスト名は静的Webサイトを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/6f7b9c3d-7214-f296-913a-78ca44af31c5.png)

なお、推奨されているAzure Front DoorとCDNを比較したが、Front Doorは機能が豊富で[WAFが標準](https://blog.shibayan.jp/entry/20220403/1648979511#google_vignette)でついてくる代わりに価格が5000円～。お試しに払うには高いので今回はCDNを使う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/4c01e172-cccb-917b-ba0b-b588707ef1c0.png)

↑ 月20円 (1G/月)    ↓月5000円(1G/月)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/011224d4-f599-4b32-8a0e-60d904db30ce.png)

## CDNの設定
作成したエンドポイントの「設定 ＞ ルールエンジン」を選択。
「規則の追加」を押し、設定を追加する。

規則名|wsa
--|--

**条件:要求ヘッダー**

ヘッダー名|演算子|ヘッダー値|大文字と小文字の変換
--|--|--|--
Origin|次の値に等しい|https://piyo.5.azurestaticapps.net|変換なし

**アクション:要求ヘッダーの変更**

アクション|HTTPヘッダー名|HTTPヘッダーの値
--|--|--
追加|cross-origin-resource-policy|cross-origin
追加|Access-Control-Allow-Origin|https://piyo.5.azurestaticapps.net

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/58b18e2b-ec73-0c40-ae40-db701b3eff91.png)

## 動作確認

CDN経由のＵＲＬに書き換える
```diff
-  const res = await fetch('https://hoge.z11.web.core.windows.net/fuga.png', {
+  const res = await fetch('https://moga.azureedge.net/fuga.png', {
    mode: 'cors',
  });
```
  

ここまでの設定をしたら、クロスオリジンでのＣＯＲＳ対応が完了し、画像がfetchできることを確認した。

## 参考

[Azure Storage + Azure CDN でCORP対策を行う](https://qiita.com/masayakomono/items/e923e04f829ce3ce5bcc)

## 追記：キャッシュを何とかしたい
画像を更新しても反映されないので、キャッシュを削除したくなりました。
CORS対応のアクセス（リクエストにOriginが含まれている）ではキャッシュが効かないようにする。


### CDNファイルのキャッシュを無効にする

CDNは[キャッシュのバイパス](https://learn.microsoft.com/ja-jp/azure/cdn/cdn-caching-rules)を設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/33231d62-b229-fc88-2267-3fd76d914850.png)

### フロントエンド側で画像読み込み時にOriginリクエストをする
imgタグからの取得でも`origin`の要求ヘッダが入るように、`crossOrigin`属性を追加する。[*](https://dev.classmethod.jp/articles/img-js-cors/#toc-5)

```diff
- <img src={src} height={100}  />
+ <img src={src} height={100} crossOrigin="anonymous" />
```

### aタグでのダウンロードはOriginがつかないので置き換える

```diff
-     <a href={`${zipUrl}`}>
        <Button
          icon="download"
          style={{ marginLeft: '5px' }}
+          onClick={async () => {
+            const res = await fetch(`${zipUrl}`, { mode: 'cors' });
+            const blob = await res.blob();
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = `${data.name}.zip`;
+            a.click();
+          }}
        >
          ユドナリウム用ZipDL
        </Button>
-     </a>
```