imgタグとJavaScriptそれぞれで同じ画像にアクセスした際に Chrome でのみ CORS エラーになる場合の対処
西田@大阪@MAD事業部です
今回は Chrome で1つの画像を同じURLで <img>
タグと JavaScript とそれぞれでダウンロードした時に、JavaScriptでのリクエストがCORSエラーになる現象に遭遇したので、その原因を調査しました
現象
WEB
(上記図でweb.example.com)から ASSETS
(上記図 で assets.example.com) に <img>
タグの src 属性に設定して画像をダウンロードした場合は成功して、 JavaScript で fetch
関数等を用いてダウンロードした場合は CORS Error になってしまいます
原因
Chrome が<img>
タグで画像をダウンロードした際にHTTPヘッダも含めたレスポンスをキャッシュし、JavaScriptでダウンロードしようとした際にHTTPヘッダも含めてそのまま返してしまうのが原因でした。これは Firefox など他のブラウザでは再現しません
WEB
からJavaScriptで ASSETS
にアクセスするにはクロスオリジンのため、CORS関連のHTTPヘッダが ASSETS
から返却される必要があります。
しかし、画像を <img>
タグで取得する際には、CORSが必要ないためChromeはOrigin ヘッダを送信しません。
Originヘッダが送信されないと、S3はCORSの設定されていても CORS 関連のHTTPヘッダをレスポンスに含めません。参考
ChromeがHTMLを描画時、<img>
タグで画像をダウンロードした際に CORS ヘッダがないレスポンスをキャッシュし、2回目以降のJavaScriptでのダウンロード時に画像をサーバーに取得にいかず、CORSのHTTPヘッダがないキャッシュされたレスポンスを返してしまいます
エラーになる具体例
イメージを具体的にしてもらうために、例を上げると、以下のHTMLをChromeで表示し <button>
をクリックした際にCORSエラーになります
<img src="https://assets.example.com/hanako.png"/> <button onclick="fetch('https://assets.example.com/hanako.png')">fetch</button>
対策
<img>
タグに crossorigin
属性をつけることで解決します
<img src="..." crossorigin="anonymous">
crossorigin
属性をつけると、 <img>
タグで画像をダウンロードするリクエストにも Origin
ヘッダをつけて送信するようになり、CORS関連のHTTPヘッダがある状態でキャッシュされるようになります
crossorigin属性について
最後にcrossorigin属性について少しハマったので補足しておきます
crossorigin属性に設定できる値
値 | 説明 |
---|---|
anonymous | crossorigin 属性を設定した要素のリクエストの資格情報モード(credentials mode) が same-origin になります |
use-crendentials | crossorigin 属性を設定した要素のリクエストの資格情報モード(credentials mode)が include になります |
参考: HTML crossorigin 属性 - HTML: HyperText Markup Language | MDN
anonymous
Cookie、クライアント証明書、認証ヘッダーの情報をSame Originの場合のみ送信します。
理解のために実際に動かして確かめていきます
画像を配信している、ASSETS
側のドメインに Cookie 設定用の以下のHTMLを配置し、ブラウザからアクセスします
<script>document.cookie = "test2=hello;SameSite=None;Secure"<script>
この状態で、画像のURLをそのままブラウザで開くと、 Cookie が送信されていることが確認できます
※ Same Origin だと crossorigin
属性を設定しなくても Cookie は送信されます
Cookie が設定された状態で ASSETS
の画像が埋め込まれている WEB
のHTMLをブラウザで開くとリクエストに Origin ヘッダが含まれているが、Cookieが含まれていないことが確認できます
<img src="..." crossorigin="anonymous">
use-credentials
Cookie、クライアント証明書、認証ヘッダーの情報をクロスオリジンでも送信します
anonymous
の時と同じようにして Cookie を保存します
その状態で crossorigin
属性に use-credentials
を設定し、WEB
のHTMLの中に埋め込みアクセスします
<img src="..." crossorigin="use-credentials"/>
すると、 origin
ヘッダに加え、Cookieも送信されている事が確認できます
ただし、 use-credentials
を設定している時に Access-Control-Allow-Origin
に *
が設定されている場合以下のエラーになりました
web.example.com Access to image at 'https://assets.example.com/hanako.png' from origin 'https://web.example.com' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
これは資格情報モード(credentials mode)が include
の時に Acccess-Control-Allow-Origin
に *
(ワイルドカード)が設定できないためです。
参考 https://fetch.spec.whatwg.org/#cors-protocol-and-credentials
*
をやめて正しくOriginを指定する必要があります
今回の例で言うと https://assets.example.com
と指定し *
がブラウザに返却されないようにする必要があります