Cross-Origin Resource Sharing (CORS)
とは何か
Cross-Origin Resource Sharing (CORS)は、他のオリジンへのリクエスト(XMLHttpRequest(XHR)
, <img>
etc.)に対し、「どのオリジンに」「どういうリクエストを」許可するか指定することで、悪意あるサイトへ情報が漏洩するリスクから自サイトとそのユーザを保護できるようにした仕組みのことである。
Cross-Origin Resource Sharing (CORS)は、Webブラウザによって自動的に(一部はXHR
/Image
オブジェクトの設定値・プロパティ値に応じて適宜)設定される(アクセス許可を要求するための)リクエストヘッダと、サーバ側が(アクセスを許可する内容としての)応答として返すレスポンスヘッダとで構成される。
Cross-Origin Resource Policy (CORP)との違い
Cross-Origin Resource Sharing (CORS)は、(クロスオリジンリクエストがCross-Origin Resource Policy (CORP)によって制限されていないことを前提に)クロスオリジンリクエストに対して「どのオリジンから」「どのような」リクエストを許可するかを設定するもの。
一方Cross-Origin Resource Policy (CORP)は、もっとそれ以前の「そもそもクロスオリジンリクエストを許可するのかどうか」を制限する。
(つまり、Cross-Origin Resource Policy
ヘッダにsame-site
/same-origin
が設定されると、クロスオリジンリクエストそのものがまるごと禁止されてる状態。)
プリフライト(preflight)リクエスト
Cross-Origin Resource Sharing (CORS)では、プリフライト(preflight)リクエストと言って、実際のGET
やPOST
リクエストなどを送信する前に事前リクエストを(OPTIONS
メソッドで)送信し、リクエスト先Webサーバに対してCORSの対応状況や許可ポリシーを事前確認するがある。
その事前確認のリクエストのことを「プリフライト(preflight)リクエスト」と呼ぶ。
プリフライトリクエストは、その必要がある場合にWebブラウザによって自動的に行われる。
プリフライトリクエストによって当該リクエストが許可されていない(ex: 自オリジンがAccess-Control-Allow-Origin
に含まれていない etc.)とわかった場合、Webブラウザは本来のリクエストの発行されないまま、リクエストは失敗する。
例えば以下のようなときに、事前にプリフライトリクエストが発行されている場合がある
-
GET
以外のメソッドでリクエストしたとき - リクエストヘッダをカスタマイズしてリクエストしたとき
参考
設定方法
サーバサイド
サーバサイドから以下のHTTPレスポンスヘッダを必要に応じて設定する。
Access-Control-Allow-Origin
どのオリジンからのアクセスを許可するかを明示する。
Access-Control-Allow-Origin: <origin> | *
指定可能な値は以下(制限が厳しい(i.e. 安全な)順)
-
<origin>
: 許可するオリジン -
*
(ワイルドカード): 「全てのオリジンを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は使用できない(=ワイルドカードではなく、どのオリジンを許可するかを明示する必要がある)
対となるリクエストヘッダ: Origin
どのオリジンからのリクエストかを表すリクエストヘッダで、Webブラウザによって自動的に付加される。
Origin: <origin>
サーバサイドでAccess-Control-Allow-Origin
の値をセットする際に、クライアントから送られたOrigin
ヘッダの値を見てセットするような実装を行う場合がある。
Access-Control-Allow-Methods
クロスオリジンリクエストに対し、許可するリクエストメソッドを明示・列挙する。
Access-Control-Allow-Methods: <method>[, <method>]*
指定可能な値は以下(制限が厳しい(i.e. 安全な)順)
-
<method>
: 許可するメソッド(コンマ区切り) -
*
(ワイルドカード): 「全てのオリジンを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は使用できない(=ワイルドカードではなく、どのメソッドを許可するかを明示する必要がある)
対となるリクエストヘッダ: Access-Control-Request-Method
どのメソッドでのリクエストを行うのかを表すリクエストヘッダで、プリフライトリクエスト時にWebブラウザによって自動的に付加される。
Access-Control-Request-Method: <method>
Access-Control-Allow-Headers
クロスオリジンリクエストに対し、許可するリクエストヘッダを明示・列挙する。
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
指定可能な値は以下(制限が厳しい(i.e. 安全な)順)
-
<header-name>
: 許可するリクエストヘッダ(コンマ区切り) -
*
(ワイルドカード): 「(Authorization
ヘッダを除く)全てのリクエストヘッダを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は特別な意味のない "*" というヘッダー名として扱われる。また、このワイルドカードを使ってもAuthorization
だけはワイルドカードの対象には含まれず別途明示的に列挙する必要がある。
対となるリクエストヘッダ: Access-Control-Request-Headers
どんなヘッダをリクエストに含めたいのかを表すリクエストヘッダで、プリフライトリクエスト時にWebブラウザによって自動的に付加される。
Origin: <origin>
Access-Control-Expose-Headers
クロスオリジンリクエストを発行したクライアントに対し、ヘッダ情報を開示するレスポンスヘッダを明示・列挙する。
ここに列挙されたレスポンスヘッダは、JavaScript側から値を参照することが出来る。
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
指定可能な値は以下(制限が厳しい(i.e. 安全な)順)
-
<header-name>
: 許可するレスポンスヘッダ(コンマ区切り) -
*
(ワイルドカード): 「(Authorization
ヘッダを除く)全てのレスポンスヘッダを許可する」ということ。なお、Cookieなどのクレデンシャル情報を含む場合は特別な意味のない "*" というヘッダー名として扱われる。また、このワイルドカードを使ってもAuthorization
だけはワイルドカードの対象には含まれず別途明示的に列挙する必要がある。
CORS セーフリストレスポンスヘッダー
Access-Control-Expose-Headers
に明示されていなくても、クライアントからアクセスされても安全とみなされるいくつかのヘッダがある。
これらを「CORS セーフリストレスポンスヘッダー」と呼ぶ。
CORS セーフリストレスポンスヘッダーは以下の通り。
Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
Access-Control-Allow-Credentials
クロスオリジンリクエストに対し、送信されてきた(Cookieなどの)クレデンシャル情報を使ってレスポンスを生成することを許可しているかを表す。
クロスオリジンリクエストにおいてこのレスポンスヘッダが返ってこなかった場合、Webブラウザは当該リクエストのレスポンスを無視する(JavaScript側からレスポンスが見れなくなる)。
Access-Control-Allow-Credentials: true
Access-Control-Max-Age
クロスオリジンリクエストに対し、プリフライトリクエストのレスポンスのキャッシュ期限を指定する。
Access-Control-Max-Age: <delta-seconds>
<delta-seconds>
: キャッシュ期限(秒)
クライアントサイド
クライアントサイドにおいて、CORSを意識する場面として以下がある。
XMLHttpRequest
のwithCredentials
プロパティ
XMLHttpRequest
(XHR
; いわゆるAjax
)において、CookieなどのクレデンシャルをもちいてCORSを行う場合、XMLHttpRequest
のwithCredentials
プロパティにtrue
を設定する必要がある。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
これを設定しないと、ブラウザはCORSリクエストにCookieを付加してくれない。
参考
Fetch API
のmode: 'cors'
オプションとcredentials
オプション
Fetch API
でCORSリクエストを正常に行うには、mode
オプションにcors
を指定する必要がある。
また、それにCookieなどのクレデンシャルを含める場合は、credentials
にinclude
を指定する。
// Fetch APIでCookieを使ったCORSリクエストを発行
const response = fetch('https://example.com', {
mode: 'cors',
credentials: 'include'
});
参考
- Fetch の使用 - Web API | MDN
- WindowOrWorkerGlobalScope.fetch() - Web API | MDN
- Fetch Standard #example-cors-with-credentials
<img>``crossorigin
属性、およびImage
のcrossOrigin`プロパティ
これを設定することで当該HTML要素が発行するリクエストを、CORSリクエスト化出来る。
Cookieなどのクレデンシャルを含める場合はuse-credentials
を、含めない場合はanonymous
を設定する。
なお、空文字や認識できない文字はanonymous
扱いされる。
<!-- クレデンシャルを含める場合 -->
<img src="https://example.com" crossorigin="use-credentials">
<script>
// 上記HTMLと同じ
const img = new Image()
img.src = 'https://example.com'
img.crossOrigin = 'use-credentials'
</script>
<!-- クレデンシャルを含めない場合 -->
<img src="https://example.com" crossorigin>
<img src="https://example.com" crossorigin="">
<img src="https://example.com" crossorigin="anonymous">
<script>
// 上記HTMLと同じ
const img = new Image()
img.src = 'https://example.com'
img.crossOrigin = 'anonymous'
// or img.crossOrigin = ''
</script>
なお、Canvasで別オリジンの画像を描画した際の具体的な影響としては、
CORS設定がないとtoDataURL()
メソッドなどで画像データを取り出そうとしたときにSecurityError
で失敗する(i.e. データを取り出さず描画するだけなら問題ない)
などである。
CORS×画像×Canvasで時々起こったりするハマりポイントと対処法
Webブラウザには、過去のリクエストをキャッシュによって描画を高速化する機能がある。そしてキャッシュはその時のレスポンスデータだけでなくどのようなリクエストヘッダ・レスポンスヘッダが送受信されたかも保存されている。
これが時として、「CORSで画像をリクエストしたにもかかわらず、過去にCORSでない方法で取得した画像データがキャッシュによって返される」という事象が起こることがある。
そして前述の通り、CORS上不完全な設定で取得した画像はCanvasでdrawImage()
で画像編集するなどのあとtoDataURL()
メソッドなどで画像データを取り出そうとしたときにSecurityError
で失敗する。
これによる不測の不具合発生を防ぐには、画像のリクエストURLにキャッシュバスターを付加するなどでキャッシュを回避する手法が有効。
参考
- : 画像埋め込み要素 - HTML: HyperText Markup Language | MDN #attr-crossorigin
- HTML crossorigin 属性 - HTML: HyperText Markup Language | MDN