あるリソースが、始めに自身を提供したドメインとは異なるドメインのリソースを要求するとき、そのリソースはクロスオリジン HTTP リクエストを行います。例えば https://domain-a.com から読み込まれた HTML ページが、https://domain-b.com/image.jpg に対して <img> src
でリクエストを行う場合です。今日の Web 上では、多くのページが CSS スタイルシートや画像、スクリプトといったリソースを別のドメインから読み込んでいます。
セキュリティ上の理由からブラウザは、スクリプトによって開始されるクロスオリジン HTTP リクエストを制限します。例えば、XMLHttpRequest は同一オリジンポリシー (same-origin policy)に従います。よって、XMLHttpRequest を使用する Web アプリケーションは自身のドメインに限り HTTP リクエストを実行できます。Web アプリケーションを改良するため、開発者はブラウザベンダーに XMLHttpRequest のクロスドメインリクエストを許可するよう求めました。
W3C の Web Applications Working Group が、新たに Cross-Origin Resource Sharing (CORS) 勧告を提示しました。CORS は、Web サーバーがドメインをまたぐアクセスを制御する方法を規定することで、ドメイン間の安全な通信を保証するというものです。CORS を使用する現行のブラウザは、クロスオリジン HTTP リクエストの危険性を緩和するために XMLHttpRequest のような API コンテナ 内で CORS を使用します。
この記事の対象者は Web 管理者、サーバー開発者、フロントエンド開発者です。現行のブラウザはヘッダやポリシーの強制を含む、クロスオリジン共有のクライアント側コンポーネントを扱います。しかしこの新しい標準仕様は、サーバーが新たなリクエストヘッダやレスポンスヘッダを扱わなければならないことを意味します。サーバー開発者向けの補足記事として、サーバーの観点から見たクロスオリジン共有 (PHP コードスニペットを含む) を説明しています。
Cross-Origin Sharing 標準仕様は、以下のような クロスサイト HTTP リクエストを可能にするために使用します:
- 前述のとおり、クロスサイト方式で
XMLHttpRequest
API を実行する。 - Web フォント (CSS の
@font-face
でクロスドメインのフォントを利用するため)。これによりサーバーは、許可した Web サイトのみクロスサイトの読み込みや利用ができる TrueType フォントを展開できます。 - WebGL テクスチャ。
- drawImage を使用して canvas に描画する画像やビデオフレーム。
- スタイルシート (CSSOM アクセス)。
- スクリプト (unmuted exceptions)
この記事では Cross-Origin Resource Sharing の全般的な説明と合わせて、Firefox 3.5 で実装した HTTP ヘッダについても説明します。
概要
Cross-Origin Resource Sharing 標準仕様は、Web ブラウザを使用して情報を読み取ることが許可された生成元のセットをサーバが示すことを可能にする、新たな HTTP ヘッダを追加することによって動作します。加えて、ユーザー情報において副作用を引き起こすことがある HTTP リクエストメソッド (特に GET
以外の HTTP メソッド、あるいは特定の MIME タイプを伴う POST
) のために、仕様書ではブラウザが HTTP の OPTIONS
リクエストメソッドを使用してサーバーに対してサポートしているメソッド要求する "プリフライト" を行って、サーバーの "認可" に基づき実際のリクエストを実際の HTTP メソッドにより送信することを認めています。サーバーは、"クレデンシャル" (Cookie や HTTP 認証データを含む) をリクエストとともに送信するべきかをクライアントに通知することもできます。
以降の章ではシナリオの説明に加え、HTTP ヘッダも詳しく説明します。
アクセス制御シナリオの例
ここでは、Cross-Origin Resource Sharing がどのように動作するかを説明する 3 つのシナリオを提示します。これらの例はすべて XMLHttpRequest
オブジェクトを用いており、アクセス制御をサポートするブラウザでクロスサイトのアクセスを行うために使用できます。
本章にある JavaScript の例示コード (およびクロスサイトリクエストを適切に処理するサーバー側コードの実例) は こちらの "in action" にあり、クロスサイトの XMLHttpRequest
をサポートするブラウザで動作するでしょう。サーバー側から見た Cross-Origin Resource Sharing の説明 (PHP の例示コードを含む) は こちらにあります。
シンプルなリクエスト
シンプルなクロスサイトリクエストとは、以下のすべての条件に合うものです:
- 許可されたメソッドは以下に限る:
GET
HEAD
POST
- ユーザエージェントが自動的に設定するヘッダ (
Connection
やUser-Agent
など) を除き、手動で設定できるヘッダは以下に限る:Accept
Accept-Language
Content-Language
Content-Type
Content-Type
ヘッダで許可される値は以下に限る:application/x-www-form-urlencoded
multipart/form-data
text/plain
例えば、ドメイン https://foo.example
にある Web コンテンツがドメイン https://bar.other
にあるコンテンツを呼び出したいと仮定します。以下のようなコードを、foo.example に置かれる JavaScript 内で使用するでしょう:
var invocation = new XMLHttpRequest(); var url = 'https://bar.other/resources/public-data/'; function callOtherDomain() { if(invocation) { invocation.open('GET', url, true); invocation.onreadystatechange = handler; invocation.send(); } }
この場合ブラウザはサーバーに何を送信し、またサーバーからのレスポンスはどうなるかを見てみましょう:
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: https://foo.example/examples/access-control/simpleXSInvocation.html Origin: https://foo.example HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml [XML Data]
1 - 10 行目は、Firefox 3.5 が送信したヘッダです。ここに記載した中で主要な HTTP ヘッダは、10 行目の Origin:
ヘッダであることに注意してください。これは、呼び出しがドメイン https://foo.example
にあるコンテンツから来たことを表します。
13 - 22 行目はドメイン https://bar.other
にあるサーバーからの HTTP レスポンスです。レスポンスでは、サーバーが 16 行目にある Access-Control-Allow-Origin:
ヘッダを返します。Origin:
ヘッダと Access-Control-Allow-Origin:
ヘッダの使用は、もっともシンプルな形のアクセス制御プロトコルを表します。このケースでは、サーバーはリソースがすべてのドメインからクロスサイト方式でアクセスできることを意味する Access-Control-Allow-Origin: *
を伴って返答しています。https://bar.other
のリソース所有者がリソースへのアクセスを https://foo.example
からだけに制限したい場合は、以下のように返答します:
Access-Control-Allow-Origin: https://foo.example
ここで、https://foo.example
(前出の 10 行目のようにリクエストの ORIGIN: ヘッダで識別されます) 以外にクロスサイト方式でリソースにアクセスできるドメインはないことに注意してください。Access-Control-Allow-Origin
ヘッダには、リクエストの Origin
ヘッダで送られた値を含むべきです。
プリフライトリクエスト
シンプルなリクエスト (前述) とは異なり、"プリフライト" リクエストは始めに、実際のリクエストを送信しても安全かを確かめるために他ドメインのリソースへ向けて OPTIONS
メソッドを使用して HTTP リクエストを送信します。クロスサイトリクエストはユーザーデータに影響を与える可能性があるため、このようにプリフライトを行います。特に以下のようなリクエストでプリフライトを行います:
GET
、HEAD
、POST
以外のメソッドを使用した場合。またapplication/x-www-form-urlencoded
、multipart/form-data
、またはtext/plain
以外の Content-Type とともにPOST
を使用してリクエストを行う場合、例えばapplication/xml
またはtext/xml
を使用してPOST
で XML のペイロードをサーバーへ送るときは、リクエストでプリフライトを行います。- カスタムヘッダをリクエストに設定した場合 (例えば、
X-PINGOTHER
のようなヘッダを用いるリクエスト)。
注記: Gecko 2.0 より、text/plain
、application/x-www-form-urlencoded
、および multipart/form-data
の各データエンコード形式を、プリフライトなしにクロスサイト送信できます。以前は、text/plain
だけがプリフライトなしに送信可能でした。
この種類の呼び出し例は以下のとおりです:
var invocation = new XMLHttpRequest(); var url = 'https://bar.other/resources/post-here/'; var body = '<?xml version="1.0"?><person><name>Arun</name></person>'; function callOtherDomain(){ if(invocation) { invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/xml'); invocation.onreadystatechange = handler; invocation.send(body); } } ......
上記の例の 3 行目では、8 行目の POST
で送信する XML の本体を作成しています。また 9 行目では、"カスタム" (非標準) HTTP リクエストヘッダ (X-PINGOTHER: pingpong
) を設定しています。このようなヘッダは HTTP/1.1 プロトコルに含まれていませんが、Web アプリケーションで一般的に有用なものです。リクエスト (POST
) で application/xml
の Content-Type を使用するため、およびカスタムヘッダを設定しているため、このリクエストではプリフライトを行います。
クライアントとサーバーとの間のやりとりの全容を見てみましょう:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
上記の 1 - 11 行目は OPTIONS
メソッドによるプリフライトを表します。Firefox 3.1 は前出の JavaScript コードで使用しているリクエストのパラメータに基づいて、プリフライトの送信が必要であることを判断します。これによりサーバーは実際のリクエストパラメータによって送られるリクエストを受け入れ可能かを応答できます。OPTIONS はサーバーから付加的な情報を得るために用いる HTTP/1.1 のメソッドであり、またべき等、つまりリソースを変更するためには使用できないメソッドです。OPTIONS リクエストと合わせて、他にリクエストヘッダを 2 つ送信していることに注意してください (それぞれ 10 行目と 11 行目です):
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method
ヘッダは、実際のリクエストは POST
リクエストメソッドで送られることを、プリフライトリクエストの一部でサーバーに通知します。Access-Control-Request-Headers
ヘッダは、実際のリクエストに X-PINGOTHER
カスタムヘッダおよび Content-Type カスタムヘッダが含まれることをサーバーに通知します。ここでサーバーは、この状況下でリクエストの受け入れを望むかを判断する機会を得ます。
上記の 14 - 26 行目はサーバーが返すレスポンスであり、リクエストメソッド (POST
) とリクエストヘッダ (X-PINGOTHER
) を受け入れられることを示しています。特に、17 - 20 行目を見てみましょう:
Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400
サーバーは Access-Control-Allow-Methods
を返しており、これは当該リソースへの問い合わせで POST
、GET
、および OPTIONS
が実行可能なメソッドであることを伝えます。このヘッダは HTTP/1.1 の Allow: レスポンスヘッダ に似ていますが、こちらはアクセス制御でのみ使用されることに注意してください。
またサーバーは、値が X-PINGOTHER
である Access-Control-Allow-Headers
を送信しています。これは実際のリクエストで使用できるヘッダであることを認めるものです。Access-Control-Allow-Methods
と同様に、Access-Control-Allow-Headers
は受け入れ可能なヘッダをコンマ区切りのリストで表します。
最後に Access-Control-Max-Age
は、プリフライトリクエストを再び送らなくてもいいように、プリフライトのレスポンスをキャッシュしてよい時間を秒数で与えます。この例では 86400 秒、つまり 24 時間です。ただしこの値には ブラウザごとに設定されている上限値 が適用されます。
クレデンシャルを含むリクエスト
XMLHttpRequest
とアクセス制御により公になるもっとも興味深い機能は、HTTP Cookie や HTTP 認証情報を認識する、"クレデンシャルを含む" リクエストを作成できることです。デフォルトではクロスサイト XMLHttpRequest
の呼び出し時に、クレデンシャルの送信ができません。XMLHttpRequest
オブジェクトを呼び出すときに特定のフラグをセットする必要があります。
以下の例では https://foo.example
から読み込まれた元のコンテンツが、https://bar.other
にあるリソースに対して Cookie をセットしたシンプルな GET リクエストを行います。foo.example のコンテンツは以下のような JavaScript を含むでしょう:
var invocation = new XMLHttpRequest(); var url = 'https://bar.other/resources/credentialed-content/'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); } }
7 行目に Cookie を含めて呼び出すためにセットしなければならない XMLHttpRequest
のフラグ、すなわち真偽値 withCredentials
があります。デフォルトでは、Cookie なしで呼び出しを行います。これはシンプルな GET
リクエストであるためプリフライトは行いませんが、ブラウザは Access-Control-Allow-Credentials: true
ヘッダを持たないレスポンスを拒否し、呼び出している Web コンテンツが使用できるレスポンスを作成しません。
以下はクライアントとサーバーとの間のやりとりの例です:
GET /resources/access-control-with-credentials/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: https://foo.example/examples/credential.html Origin: https://foo.example Cookie: pageAccess=2 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:34:52 GMT Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2 X-Powered-By: PHP/5.2.6 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Credentials: true Cache-Control: no-cache Pragma: no-cache Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 106 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain [text/plain payload]
11 行目に https://bar.other
向けの Cookie が含まれていますが、bar.other が Access-Control-Allow-Credentials: true
(19 行目) をレスポンスに含めなければ、レスポンスは無視され Web コンテンツで使用できません。重要: クレデンシャルを含むリクエストへ応答するとき、サーバーはドメインを指定しなければならず、ワイルドカードは使用できません。ヘッダで Access-Control-Allow-Origin: *
のようにワイルドカードを使用すると、上記の例は失敗します。Access-Control-Allow-Origin
で https://foo.example
を明示しているため、クレデンシャルを認識したコンテンツが呼び出し元の Web コンテンツ側に返ります。22 行目でさらに Cookie を設定していることに注目してください。
これらすべての例の動作を こちらで確認できます。次章では、実際の HTTP ヘッダについて説明します。
HTTP レスポンスヘッダ
ここでは Cross-Origin Resource Sharing の仕様で定義されている、アクセス制御のためにサーバーが返す HTTP レスポンスヘッダを掲載します。前の章では、これらの実際の動作の概要を説明しました。
Access-Control-Allow-Origin
返されたリソースには、下記構文の Access-Control-Allow-Origin
ヘッダがあります。
Access-Control-Allow-Origin: <origin> | *
パラメータ origin
は、そのリソースにアクセスしてよい URI を指定します。ブラウザはこれを守らなければなりません。クレデンシャルを含まないリクエストにおいて、サーバーはワイルドカードとして "*" を指定でき、それによりすべてのオリジンにリソースへのアクセスを許可します。
例えば https://mozilla.com にリソースへのアクセスを許可するには、以下のように指定します:
Access-Control-Allow-Origin: https://mozilla.com
サーバーが "*" 以外のオリジンホストを指定した場合は、Origin
リクエストヘッダの値に応じてサーバのレスポンスが異なることをクライアントへ示すために、Vary
レスポンスヘッダに Origin
を含めなければなりません。
Access-Control-Expose-Headers
Gecko 2.0 が必要(Firefox 4 / Thunderbird 3.3 / SeaMonkey 2.1)このヘッダで、ブラウザが使用できるヘッダのホワイトリストをサーバーが示すことができます。例えば:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
これは、ブラウザに対して X-My-Custom-Header
および X-Another-Custom-Header
ヘッダを許可します。
Access-Control-Max-Age
このヘッダはプリフライトの結果をキャッシュしてよい時間を示します。プリフライトリクエストの例は、前出の例をご覧ください。
Access-Control-Max-Age: <delta-seconds>
パラメータ delta-seconds
は、結果をキャッシュしてよい時間を秒数で示します。
Access-Control-Allow-Credentials
credentials
フラグが真であるときに、リクエストへのレスポンスを開示してよいか否かを示します。プリフライトリクエストのレスポンスの一部として用いたときは、実際のリクエストでクレデンシャルを使用してよいか否かを示します。シンプルな GET
リクエストはプリフライトを行いませんので、リソースへのリクエストがクレデンシャル付きで行われた場合にリソースと共にこのヘッダを返さなければ、レスポンスはブラウザによって無視されて Web コンテンツに返らないことに注意してください。
Access-Control-Allow-Credentials: true | false
クレデンシャルを含むリクエスト については前述のとおりです。
Access-Control-Allow-Methods
リソースへのアクセス時に許可するメソッドを指定します。これはプリフライトリクエストのレスポンスで用いられます。リクエストのプリフライトを行う条件については前述のとおりです。
Access-Control-Allow-Methods: <method>[, <method>]*
ブラウザにこのヘッダを送信する例を含む、プリフライトリクエストの例は 前出のとおりです。
Access-Control-Allow-Headers
実際のリクエストでどの HTTP ヘッダを使用できるかを示すために、プリフライトリクエスト のレスポンスで使用します。
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
HTTP リクエストヘッダ
ここでは、Cross-Origin Resource Sharing を使用するために HTTP リクエストを発行する際に、クライアントが使用できるヘッダを掲載します。サーバーへ要求を行う際に、これらのヘッダをセットすることに注意してください。クロスサイト XMLHttpRequest
機能を使用する開発者は、Cross-Origin Sharing リクエストヘッダをプログラムでセットする必要がありません。
Origin
クロスサイトのアクセスリクエストやプリフライトリクエストのオリジンを示します。
Origin: <origin>
origin は、リクエストを開始したサーバーを示す URI です。ここにパス情報は含めず、サーバー名だけにします。
origin
は空文字列にすることができます。これは、例えばオリジンが data
URLである場合などに有用です。すべてのアクセス制御リクエストにおいて、ORIGIN
ヘッダは常に送信されることに注意してください。
Access-Control-Request-Method
実際のリクエストを行う際に使用する HTTP メソッドをサーバーがわかるようにするため、プリフライトリクエストを発信する際に使用します。
Access-Control-Request-Method: <method>
使用例は 前出のとおりです。
Access-Control-Request-Headers
実際のリクエストを行う際に使用する HTTP ヘッダをサーバーがわかるようにするため、プリフライトリクエストを発信する際に使用します。
Access-Control-Request-Headers: <field-name>[, <field-name>]*
使用例は 前出のとおりです。
仕様
仕様書 | 策定状況 | コメント |
---|---|---|
Fetch CORS の定義 |
現行の標準 | CORS 仕様の置き換えを目指して、新たに定義 |
CORS | 勧告 | 最初期の定義 |
ブラウザ実装状況
機能 | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
基本サポート | 4 | 3.5 | 8 (XDomainRequest を通して) 10 |
12 | 4 |
機能 | Android | Chrome for Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|---|
基本サポート | 2.1 | yes | yes | ? | 12 | 3.2 |
注記
Internet Explorer 8 および 9 は XDomainRequest オブジェクトで CORS を提供していますが、完全な実装は IE 10 で行っています。Firefox 3.5 よりクロスサイトの XMLHttpRequests と Web フォントのサポートを始めていましたが、ある種のリクエストは後のバージョンまで制限されていました。具体的には Firefox 7 から Web GL テクスチャのクロスサイト HTTP リクエスト、Firefox 9 では drawImage を用いる Canvas への画像描画のサポートを始めました。
関連情報
- Code Samples Showing
XMLHttpRequest
and Cross-Origin Resource Sharing - Cross-Origin Resource Sharing From a Server-Side Perspective (PHP, etc.)
- Cross-Origin Resource Sharing specification
XMLHttpRequest
- Further Discussion of the Origin Header
- Using CORS with All (Modern) Browsers
- Using CORS - HTML5 Rocks