次の方法で共有


ASP.NET Web API

ASP.NET Web API 2 における CORS サポート

Brock Allen

Cross Origin Resource Sharing (CORS) とは、ブラウザーによって JavaScript に課せられる同一生成元ポリシーのセキュリティ制約を回避できるようにする World Wide Web コンソーシアム (W3C) の仕様 (通常 HTML5 に含まれると考えられます) です。この同一生成元ポリシーにより、JavaScript は AJAX 呼び出しの呼び出し先が同じ生成元の Web ページに含まれていなければ呼び出しを行うことができません (「生成元」とは、ホスト名、プロトコル、ポート番号を組み合わせたものです)。たとえば、http://foo.com の Web ページの JavaScript は http://bar.com (http://www.foo.com、https://foo.com、http://foo.com:999 でも同じ) に AJAX 呼び出しを行うことはできません。

CORS は、この制約を緩和して呼び出しを許可する生成元を指定できるようにします。CORS はブラウザーによって強制的に適用されますが、CORS をサーバーに実装しておく必要があります。ASP.NET Web API 2 の最新リリースは CORS を完全にサポートしています。Web API 2 により、生成元が異なる JavaScript クライアントから自身の API へのアクセスを許可するポリシーを構成できます。

CORS の基礎

Web API の実装は仕様に忠実なので、Web API で新しい CORS 機能を使用するには CORS そのものの詳細を理解することをお勧めします。今の時点で詳細まで理解しなくてもとお考えかもしれませんが、後ほど Web API で利用できる設定を理解するため、また CORS をデバッグする際に問題点を速く解決するためにも理解しておくと役に立ちます。

JavaScript から生成元をまたいで AJAX 呼び出しを試みると、まず、ブラウザーからサーバーに HTTP 要求ヘッダー (Origin など) を送信して、その AJAX 呼び出しが許可されるかどうかを "問い合わせ" るのが CORS の大まかなメカニズムです。これに対してサーバーは、応答に HTTP ヘッダー (Access-Control-Allow-Origin など) を返し、何が許可されているかを示します。クライアントから個別の URL が呼び出されるたびに、このようなアクセス許可のチェックが行われます。つまり、URL ごとに異なるアクセス許可を設定できます。

CORS により、生成元だけでなく、許可されている HTTP メソッドの種類、クライアントから送信できる HTTP 要求ヘッダーの種類、クライアントで読み取れる HTTP 応答ヘッダーの種類、および資格情報 (Cookie または承認ヘッダー) の自動送受信をブラウザーが許可されているかどうかをサーバーで示すことができます。追加の要求ヘッダーおよび応答ヘッダーにより、これらのどの機能が許可されているかが示されます。これらのヘッダーを図 1 にまとめています (要求ヘッダーがなく、応答ヘッダーのみ存在する機能もあります)。

図 1 CORS HTTP ヘッダー

アクセス許可/機能 要求ヘッダー 応答ヘッダー
生成元 Origin Access-Control-Allow-Origin
HTTP メソッド Access-Control-Request-Method Access-Control-Allow-Method
要求ヘッダー Access-Control-Request-Headers Access-Control-Allow-Headers
応答ヘッダー   Access-Control-Expose-Headers
資格情報   Access-Control-Allow-Credentials
プレフライト応答のキャッシュ   Access-Control-Max-Age

ブラウザーは、単純な CORS 要求とプレフライト CORS 要求の 2 つの異なる方法を使用して、サーバーにこれらのアクセス許可を問い合わせることができます。

単純な CORS 要求: 以下は、単純な CORS 要求の例です。

POST http://localhost/WebApiCorsServer/Resources/ HTTP/1.1
 Host: localhost
 Accept: */*
 Origin: http://localhost:55912
 Content-Type: application/x-www-form-urlencoded; charset=UTF-8
 value1=foo&value2=5

その応答です。

HTTP/1.1 200 OK
 Content-Type: application/json; charset=utf-8
 Access-Control-Allow-Origin: http://localhost:55912
 Content-Length: 27
 {"Value1":"foo","Value2":5}

これは http://localhost:55912 から http://localhost への生成元をまたがる要求です。ブラウザーは要求に Origin HTTP ヘッダーを付加することで、呼び出し元の生成元をサーバーに示しています。サーバーは Access-Control-Allow-Origin 応答ヘッダーを付加して応答し、この生成元が許可されていることを示しています。ブラウザーはサーバーのポリシーを強制的に適用し、JavaScript は通常の成功コールバックを受け取ります。

サーバーは、要求されている生成元の値そのものか、またはあらゆる生成元が許可されることを示す値 "*" のどちらかを付加して応答を返します。呼び出し元の生成元をサーバーが許可しなかった場合は単純に Access-Control-Allow-Origin ヘッダーが返されず、呼び出し元の JavaScript のエラー コールバックが呼び出されます。

単純な CORS 要求を使用しているにもかかわらず、サーバーによりエラーが呼び出されます。まだ CORS について理解の途中なので驚く方もいるかもしれませんが、この動作はブラウザーが

要素を構築し、通常の POST 要求を行うシナリオと変わりありません。CORS は、サーバーでこのような呼び出しが行われるのを防ぐのではなく、呼び出し元の JavaScript が結果を受け取るのを防ぎます。呼び出し元からサーバーが呼び出されるのを防ぐには、サーバー コードにある種のアクセス許可の承認を (場合によっては [Authorize] 承認フィルター属性を使用して) 実装することになります。

上記の例は、クライアントによる AJAX 呼び出しの種類が GET か POST のどちらかで、Content-Type は application/x-www-form-urlencoded、multipart/form-data、または text/plain のいずれかで、それ以外の要求ヘッダーは送信されないため、単純な CORS 要求と呼ばれます。AJAX 呼び出しが、別の HTTP メソッドの場合、Content-Type が上記以外の値の場合、クライアントから追加の要求ヘッダーを送信する必要がある場合、その要求はプレフライト要求と見なされます。プレフライト要求のメカニズムはやや異なります。

プレフライト CORS 要求: AJAX 呼び出しが単純な要求ではない場合、プレフライト CORS 要求が必要です。プレフライト CORS 要求は単に、アクセス許可を取得するためのサーバーへの追加 HTTP 要求です。このプレフライト要求は、ブラウザーにより自動的に行われ、OPTIONS HTTP メソッドを使用します。サーバーがプレフライト要求に正しく応答し、アクセス許可が付与されると、ブラウザーは JavaScript で行おうとしてる実際の AJAX 呼び出をが実行します。

パフォーマンスが重要な場合 (重要でない場合はありませんが)、プレフライト応答に Access-Control-Max-Age ヘッダーを含めることで、プレフライト要求の結果をブラウザーでキャッシュできます。この値には、アクセス許可をキャッシュできる秒数を含みます。

以下は、プレフライト CORS 要求の例です。

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
 Host: localhost
 Access-Control-Request-Method: PUT
 Origin: http://localhost:55912
 Access-Control-Request-Headers: content-type
 Accept: */*

そのプレフライト応答です。

HTTP/1.1 200 OK
 Access-Control-Allow-Origin: http://localhost:55912
 Access-Control-Allow-Methods: PUT
 Access-Control-Allow-Headers: content-typeAccess-Control-Max-Age: 600

以下は、実際の AJAX 要求です。

PUT http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
 Host: localhost
 Content-Length: 27
 Accept: application/json, text/javascript, */*; q=0.01
 Origin: http://localhost:55912
 Content-Type: application/json
 {"value1":"foo","value2":5}

その AJAX 応答です。

HTTP/1.1 200 OK
 Content-Type: application/json; charset=utf-8
 Access-Control-Allow-Origin: http://localhost:55912
 Content-Length: 27
 {"Value1":"foo","Value2":5}

この例の HTTP メソッドは PUT で、要求に application/json が含まれることを示す Content-Type ヘッダーをクライアントが送信する必要があるため、プレフライト CORS 要求が行われています。このプレフライト要求では、(Origin に加え) Access-Control-Request-Method 要求ヘッダーや Access-Control-Request-Headers 要求ヘッダーが使用され、クライアントが送信しようとしている HTTP メソッドの種類と追加ヘッダーへのアクセス許可を要求しています。

サーバーからアクセス許可が付与され (プレフライト キャッシュ期間が設定され)、次にブラウザーに実際の AJAX 呼び出しが許可されます。サーバーが、要求した機能のいずれかのアクセス許可を付与しなかった場合、対応する応答ヘッダーが返されず、AJAX 呼び出しは行われずに JavaScript エラー コールバックが呼び出されます。

上記の HTTP 要求および HTTP 応答は、Firefox を使用して行っています。Internet Explorer を使用すると、追加で Accept ヘッダーが要求されているのがわかります。Chrome を使用すると、Accept と Origin の両方が追加で要求されます。興味深いことに、今回の Access-Control-Allow-Headers には Accept や Origin がありません。それらのヘッダーは (Web API により) 暗黙のうちに指定され、省略できると仕様に記載されているためです。Origin と Accept を実際に要求する必要があるかは議論のポイントですが、これらのブラウザーの現時点での動作方法から、それらのヘッダーを Web API CORS ポリシーに含めることを強くお勧めします。残念ながら、ブラウザー ベンダーの仕様の受け取り方は一貫性に欠けているように思えます。

応答ヘッダー: Access-Control-Expose-Headers 応答ヘッダーを使用すれば、クライアントに応答ヘッダーへのアクセス許可を簡単に与えることができます。以下は、カスタム応答ヘッダー "bar" へのアクセスを呼び出し元の JavaScript に許可する HTTP 応答の例です。

HTTP/1.1 200 OK
 Content-Type: application/json; charset=utf-8
 Access-Control-Allow-Origin: http://localhost:55912Access-Control-Expose-Headers: bar
 bar: a bar value
 Content-Length: 27
 {"Value1":"foo","Value2":5}

JavaScript クライアントは、単純に XMLHttpRequest getResponseHeader 関数を使用して、この値を読み取ることができます。以下は、jQuery を使用した例です。

$.ajax({
   url: "http://localhost/WebApiCorsServer/Resources/1",
   // other settings omitted
 }).done(function (data, status, xhr) {
   var bar = xhr.getResponseHeader("bar");
   alert(bar);
 });

資格情報と認証: おそらく最も混乱を招く CORS の側面は、資格情報と認証を取り扱う必要があることです。通常、Web API を使用した認証は、Cookie または承認ヘッダーのいずれかを使って行います (他の方法もありますが、この 2 つが最も一般的です)。通常のブラウザーの動作では、これら 2 つのどちらかを事前に設定していると、以降の要求では暗黙のうちに設定した値がサーバーに渡されます。ただし、生成元をまたがる AJAX 呼び出しの場合、このような値の暗黙の受け渡しを JavaScript で明示的に要求する必要があり (XMLHttpRequest で withCredentials フラグを使用)、さらにサーバーの CORS ポリシーで明示的に許可する必要があります (Access-Control-Allow-Credentials 応答ヘッダーを使用)。

以下は、jQuery を使用して withCredentials フラグを設定する JavaScript クライアントの例です。

$.ajax({
   url: "http://localhost/WebApiCorsServer/Resources/1",
   xhrFields: {
     withCredentials: true
   }
   // Other settings omitted
 });

withCredentials では、2 つの処理が行われます。サーバーが Cookie を発行する場合は、ブラウザーがこれを受け入れることができます。ブラウザーが Cookie を保持する場合は、これをサーバーに送信できます。

以下は、資格情報を許可する HTTP 応答の例です。

HTTP/1.1 200 OK
 Set-Cookie: foo=1379020091825
 Access-Control-Allow-Origin: http://localhost:55912
 Access-Control-Allow-Credentials: true

Access-Control-Allow-Credentials 応答ヘッダーでは、2 つの処理が行われます。応答に Cookie が含まれる場合は、ブラウザーがこれを受け入れることができます。ブラウザーから要求で Cookie を送信する場合は、JavaScript クライアントがその呼び出しの結果を受け取ることができます。つまり、クライアントが withCredentials を設定する場合、(応答時に) サーバーが資格情報を許可すると、クライアントは JavaScript で成功のコールバックだけを確認することになります。withCredentials を設定しても、サーバーが資格情報を許可しないと、クライアントはその結果にアクセスできず、クライアントのエラー コールバックが呼び出されることになります。

承認ヘッダーが Cookie の代わりに使用される場合 (たとえば、基本認証や統合 Windows 認証を使用しているとき) も同じ一連の規則と動作が適用されます。資格情報と承認ヘッダーの使用について興味深い点は、サーバーは Access-Control-Allow-Headers CORS 応答ヘッダーで明示的に承認ヘッダーを許可する必要がないことです。

Access-Control-Allow-Credentials CORS 応答ヘッダーを使用し、サーバーが承認ヘッダーを発行する場合、ワイルドカード値 * を Access-Control-Allow-Origin で使用することはできません。代わりに CORS 仕様では、明示的な生成元を使用することを求めています。Web API フレームワークがこのすべてを処理しますが、デバッグ時にこのような動作に気づくので、ここで触れています。

資格情報と認証の説明には意外な展開があります。ここまでの説明は、ブラウザーが資格情報を暗黙のうちに送信するシナリオです。JavaScript クライアントは、明示的に資格情報を送信することもできます (この場合も通常承認ヘッダーを使用)。この場合、資格情報に関する前述の規則や動作はどれも当てはまりません。

このようなシナリオの場合、クライアントは要求に承認ヘッダーを明示的に設定することになります。XMLHttpRequest に withCredentials を設定する必要はありません。このヘッダーは、プレフライト要求をトリガーすることになります。サーバーは Access-Control-Allow-Headers CORS 応答ヘッダーを使用して承認ヘッダーを許可する必要があります。また、サーバーは Access-Control­Allow-Credentials CORS 応答ヘッダーを発行する必要もありません。

承認ヘッダーを明示的に設定する場合のクライアント コードは次のようになります。

$.ajax({
   url: "http://localhost/WebApiCorsServer/Resources/1",
   headers: {
     "Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3Mi..."
   }
   // Other settings omitted
 });

以下は、そのプレフライト要求です。

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
 Host: localhost
 Access-Control-Request-Method: GET
 Origin: http://localhost:55912
 Access-Control-Request-Headers: authorization
 Accept: */*

以下は、そのプレフライト応答です。

HTTP/1.1 200 OK
 Access-Control-Allow-Origin: *
 Access-Control-Allow-Headers: authorization

承認ヘッダーでトークン値を明示的に設定することは、クロスサイト リクエスト フォージェリ (CSRF) 攻撃の可能性を避けることになるため、安全性の高い認証手法です。Visual Studio 2013 の新しい Single Page Application (単一ページのアプリケーション、SPA) テンプレートでこの手法を確認することができます。

HTTP レベルでの CORS の基礎を確認したので、ここからは新しい CORS フレームワークを使用して、前述のヘッダーを Web API 経由で発行する方法を示します。

Web API 2 における CORS サポート

Web API の CORS サポートは、CORS 要求へのアクセス許可の定義をアプリケーションで行えるようにするための完全版のフレームワークです。このフレームワークの中核となるポリシーの概念は、開発者が、アプリケーションへの特定の要求を許可する CORS 機能を指定できるようにすることです。

まず、CORS フレームワークを取得するためには、Web API アプリケーションの CORS ライブラリを参照する必要があります (このライブラリは Visual Studio 2013 のどの Web API テンプレートからも既定では参照されていません)。Web API CORS フレームワーク は、NuGet から Microsoft.AspNet.WebApi.Cors パッケージとして利用できます。NuGet を使用しなくても、フレームワークを Visual Studio 2013 の一部として利用できますが、その場合 System.Web.Http.Cors.dll と System.Web.Cors.dll という 2 つのアセンブリを参照する必要があります (個人的に使用しているコンピューターでは、これらのアセンブリは C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages にあります)。

次に、ポリシーを表すには、Web API で EnableCorsAttribute というカスタム属性クラスを提供します。このクラスには、生成元、HTTP メソッド、要求ヘッダー、応答ヘッダー、および資格情報が認証されているかどうかのプロパティ (先ほど説明した CORS 仕様の詳細すべてのモデル) が含まれています。

最後に、Web API CORS フレームワークで CORS 要求を処理し、適切な CORS 応答ヘッダーを発行するには、アプリケーションへのすべての要求を確認する必要があります。Web API には、メッセージ ハンドラーを使用したインターセプトのための拡張ポイントがあります。いみじくも、この Web API CORS フレームワークは CorsMessageHandler というメッセージ ハンドラーを実装しています。CORS 要求の場合、呼び出されるメソッドの属性で表しされるポリシーを調べて、適切な CORS 応答ヘッダーが発行されます。

EnableCorsAttribute: EnableCorsAttribute クラスは、アプリケーションが CORS ポリシーを表現する方法を表します。EnableCorsAttribute クラスには、3 つまたは 4 つのパラメーターを受け取るオーバーロード コンストラクターがあります。パラメーター (指定順):

  1. 許可する生成元の一覧
  2. 許可する要求ヘッダーの一覧
  3. 許可する HTTP メソッドの一覧
  4. 許可する応答ヘッダーの一覧 (オプション)

また、資格情報を許可するためのプロパティ (SupportsCredentials) とプレフライト キャッシュ期間の値を指定するためのプロパティ (PreflightMaxAge) もあります。

図 2 に、EnableCors 属性をコントローラーの個別のメソッドに適用する例を示します。さまざまな CORS ポリシー設定に使用する値は、上記の例で示した CORS 要求と CORS 応答に一致します。

図 2 アクション メソッドへの EnableCors 属性の適用

public class ResourcesController : ApiController
 {
   [EnableCors("http://localhost:55912", // Origin
               null,                     // Request headers
               "GET",                    // HTTP methods
               "bar",                    // Response headers
               SupportsCredentials=true  // Allow credentials
   )]
   public HttpResponseMessage Get(int id)
   {
     var resp = Request.CreateResponse(HttpStatusCode.NoContent);
     resp.Headers.Add("bar", "a bar value");
     return resp;
   }
   [EnableCors("http://localhost:55912",       // Origin
               "Accept, Origin, Content-Type", // Request headers
               "PUT",                          // HTTP methods
               PreflightMaxAge=600             // Preflight cache duration
   )]
   public HttpResponseMessage Put(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   [EnableCors("http://localhost:55912",       // Origin
               "Accept, Origin, Content-Type", // Request headers
               "POST",                         // HTTP methods
               PreflightMaxAge=600             // Preflight cache duration
   )]
   public HttpResponseMessage Post(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
 }

コンストラクターの各パラメーターは文字列です。(図 2 の要求ヘッダーの許可で示したように) コンマ区切りのリストを指定することで複数の値を示します。すべての生成元、要求ヘッダー、または HTTP メソッドを許可するのであれば、値に * を使用できます (この場合も応答ヘッダーを明示する必要があります)。

EnableCors 属性のアプリケーションへの適用は、メソッド レベルだけでなく、クラス レベルやグローバルで行うこともできます。この属性を適用したレベルは、Web API コードでそのレベル以下のすべての要求の CORS を構成します。たとえば、メソッド レベルで適用すると、ポリシーはそのアクションの要求にしか適用されません。一方、クラス レベルで適用すると、ポリシーはそのクラスのコントローラーへのすべての要求に適用されます。グローバルで適用すると、ポリシーはすべての要求に適用されます。

以下は、クラス レベルで属性を適用するもう 1 つの例です。この例で使用する設定は、許可する生成元、要求ヘッダーおよび HTTP メソッドに対してワイルドカードを使用するため、まったく寛容な設定とになります。

[EnableCors("*", "*", "*")]
 public class ResourcesController : ApiController
 {
   public HttpResponseMessage Put(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   public HttpResponseMessage Post(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
 }

ポリシーが複数か所に存在する場合、"一番近い" 属性が使用され、他の属性は無視されます (そのため、優先順位はメソッド、クラス、グローバルの順です)。ポリシーを上のレベルには適用しても、下のレベルの要求からは除外する場合、DisableCorsAttribute という別の属性クラスを使用します。この属性は、要するに、アクセス許可を付与しないポリシーです。

コントローラーに CORS を許可しない他のメソッドが存在する場合、2 つのオプションのどちらかを使用できます。1 つは、HTTP メソッドの一覧を明示する方法です (図 3 参照)。もう 1 つは、ワイルドカードを残し、DisableCors 属性を使用して Delete メソッドを除外する方法です (図 4参照)。

図 3 HTTP メソッドに明示的な値を使用

[EnableCors("*", "*", "PUT, POST")]
 public class ResourcesController : ApiController
 {
   public HttpResponseMessage Put(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   public HttpResponseMessage Post(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   // CORS not allowed because DELETE is not in the method list above
   public HttpResponseMessage Delete(int id)
   {
     return Request.CreateResponse(HttpStatusCode.NoContent);
   }
 }

図 4 DisableCors 属性の使用

[EnableCors("*", "*", "*")]
 public class ResourcesController : ApiController
 {
   public HttpResponseMessage Put(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   public HttpResponseMessage Post(Resource data)
   {
     return Request.CreateResponse(HttpStatusCode.OK, data);
   }
   // CORS not allowed because of the [DisableCors] attribute
   [DisableCors]
   public HttpResponseMessage Delete(int id)
   {
     return Request.CreateResponse(HttpStatusCode.NoContent);
   }
 }

CorsMessageHandler: CORS ポリシーを評価して CORS 応答ヘッダーを発行するためには、要求をインターセプトするジョブを実行するように CORS フレームワークに対し CorsMessageHandler を有効にする必要があります。メッセージ ハンドラーを有効にするには、通常、アプリケーションの Web API 構成クラスで EnableCors 拡張メソッドを呼び出します。

public static class WebApiConfig
 {
   public static void Register(HttpConfiguration config)
   {
     // Other configuration omitted
     config.EnableCors();
   }
 }

グローバル CORS ポリシーを指定する場合は、EnableCorsAttribute クラスのインスタンスを EnableCors メソッドにパラメーターとして渡します。たとえば、以下のコードは、アプリケーション内でグローバルに寛容な CORS ポリシーを構成することになります。

public static class WebApiConfig
 {
   public static void Register(HttpConfiguration config)
   {
     // Other configuration omitted
     config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
   }
 }

すべてのメッセージ ハンドラー同様、CorsMessageHandler は、グローバルではなくルートごとに登録できます。

これで、ASP.NET Web API 2 の基本的な "既製の" CORS フレームワークの説明は終わりです。このフレームワークが優れている点は、動的なシナリオに拡張できることです。

ポリシーをカスタマイズする

上記の例から、生成元の一覧 (ワイルドカードを使用していない場合) が Web API コードにコンパイルされる静的な一覧であることは明らかです。これは開発中や、特定のシナリオでは機能しますが、生成元 (または他のアクセス許可) の一覧を (データベースなどから) 動的に決める必要がある場合は十分ではありません。

さいわい、Web API の CORS フレームワークは拡張性が高く、生成元の動的な一覧も簡単にサポートできます。実際、このフレームワークは非常に柔軟で、ポリシーの生成をカスタマイズする一般的な手法は 2 つあります。

カスタム CORS ポリシー属性: 動的な CORS ポリシーを実現する 1 つの手法は、データー ソースからポリシーを生成できるカスタム属性クラスを開発することです。このカスタム属性クラスは Web API で提供される EnableCorsAttribute クラスの代わりに使用することができます。この手法はシンプルで、必要に応じて特定のクラスやメソッドに属性を適用できる (他のものには適用しないで) きめ細かな使用感があります。

この手法を実装するには、単に既存の EnableCorsAttribute クラスに似たカスタム属性を構築します。最も重要なのは、特定の要求用に CorsPolicy のインスタンスを作成する役割を担う ICorsPolicyProvider インターフェイスです。図 5 に例を示します。

図 5 カスタム CORS ポリシー属性

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                 AllowMultiple = false)]
 public class EnableCorsForPaidCustomersAttribute :
   Attribute, ICorsPolicyProvider
 {
   public async Task GetCorsPolicyAsync(
     HttpRequestMessage request, CancellationToken cancellationToken)
   {
     var corsRequestContext = request.GetCorsRequestContext();
     var originRequested = corsRequestContext.Origin;
     if (await IsOriginFromAPaidCustomer(originRequested))
     {
       // Grant CORS request
       var policy = new CorsPolicy
       {
         AllowAnyHeader = true,
         AllowAnyMethod = true,
       };
       policy.Origins.Add(originRequested);
       return policy;
     }
     else
     {
       // Reject CORS request
       return null;
     }
   }
   private async Task IsOriginFromAPaidCustomer(
     string originRequested)
   {
     // Do database look up here to determine if origin should be allowed
     return true;
   }
 }

CorsPolicy クラスは、付与する CORS アクセス許可を表すプロパティをすべて含みます。ここで使用している値は単なる例ですが、これらの値はデータベース クエリ (または他の任意のソース) から動的に追加できます。

カスタム ポリシー プロバイダー ファクトリ: 動的な CORS ポリシーを構築する 2 つ目の一般的な手法は、カスタム ポリシー プロバイダー ファクトリーを作成することです。これは、現在の要求用のポリシー プロバイダーを取得する CORS フレームワークの機能です。Web API の既定の実装では、カスタム属性を使用して、ポリシー プロバイダーを検出します (先ほど見たとおり、この属性クラス自体がポリシー プロバイダーでした)。これは、CORS フレームワークのもう 1 つのプラグ可能な機能です。カスタム属性ではなくポリシー手法を採用する場合、独自のポリシー プロバイダー ファクトリを実装することになります。

前述の属性ベースの手法は、要求からポリシーへの明示的な関連付けが行われます。カスタム ポリシー プロバイダー ファクトリー手法は、着信要求をポリシーに一致させるロジックを提供するための実装が必要になる点で、属性手法と異なります。この手法は、基本的に CORS ポリシーの取得に重点が置かれていて、よりおおまかな手法になっています。

図 6 に、カスタム ポリシー プロバイダー ファクトリの記述例を示します。この例で最も重要なのは、ICorsPolicyProviderFactory インターフェイスと GetCorsPolicyProvider メソッドの実装です。

図 6 カスタム ポリシー プロバイダー ファクトリ

public class DynamicPolicyProviderFactory : ICorsPolicyProviderFactory
 {
   public ICorsPolicyProvider GetCorsPolicyProvider(
     HttpRequestMessage request)
   {
     var route = request.GetRouteData();
     var controller = (string)route.Values["controller"];
     var corsRequestContext = request.GetCorsRequestContext();
     var originRequested = corsRequestContext.Origin;
     var policy = GetPolicyForControllerAndOrigin(
       controller, originRequested);
     return new CustomPolicyProvider(policy);
   }
   private CorsPolicy GetPolicyForControllerAndOrigin(
    string controller, string originRequested)
   {
     // Do database lookup to determine if the controller is allowed for
     // the origin and create CorsPolicy if it is (otherwise return null)
     var policy = new CorsPolicy();
     policy.Origins.Add(originRequested);
     policy.Methods.Add("GET");
     return policy;
   }
 }
 public class CustomPolicyProvider : ICorsPolicyProvider
 {
   CorsPolicy policy;
   public CustomPolicyProvider(CorsPolicy policy)
   {
     this.policy = policy;
   }
   public Task GetCorsPolicyAsync(
     HttpRequestMessage request, CancellationToken cancellationToken)
   {
     return Task.FromResult(this.policy);
   }
 }

この手法の主な違いは、着信要求のポリシーの決定が完全に実装に委ねられる点です。図 6では、ポリシー値をデータベースにクエリするために、コントローラーと生成元を使用します。繰り返しになりますが、この手法は最も柔軟ですが、要求のポリシーを決定するための手間が多くなる可能性があります。

カスタム ポリシー プロバイダー ファクトリを使用するには、Web API 構成の SetCorsPolicyProviderFactory 拡張メソッドを通じて、Web API にそのファクトリを登録する必要があります。

public static class WebApiConfig
 {
   public static void Register(HttpConfiguration config)
   {
     // Other configuration omitted
     config.EnableCors();
     config.SetCorsPolicyProviderFactory(
       new DynamicPolicyProviderFactory());
   }
 }

コミュニティへの貢献

ASP.NET Web API は、オープンソース フレームワークであり、総称して ASP.NET Web スタックと呼ばれるより大きなオープンソース フレームワーク群 (MVC、Web ページなども含む) の一部です。

これらのフレームワークは ASP.NET プラットフォームを構築するために使用され、マイクロソフトの ASP.NET チームが管理しています。オープンソース プラットフォームの管理者として、ASP.NET チームは、コミュニティへの貢献を歓迎しています。Web API の Cross Origin Resource Sharing (CORS) の実装もそのような貢献の 1 つです。

CORS は当初、Brock Allen が開発していた thinktecture IdentityModel セキュリティ ライブラリの一部でした (thinktecture.github.io (英語) 参照)。

CORS をデバッグする

生成元をまたがる AJAX 呼び出しが機能しない場合に CORS をデバッグする手法はいくつかあります。

クライアント側: デバッグの 1 つの手法は単純にいずれかの HTTP デバッガー (たとえば Fiddler) を使用し、すべての HTTP 要求を調査することです。CORS の仕様の詳細についてここまでで集まった知識を基にすれば、CORS HTTP ヘッダーを調査して (または調査しなくても) なぜ特定の AJAX 要求にアクセス許可が付与されないのかを解き明かすことができるでしょう。

もう 1 つの手法は、ブラウザーの F12 開発者ツールを使用することです。最新ブラウザーのコンソール ウィンドウでは、CORS が原因で AJAX 呼び出しに失敗したときに、役立つエラー メッセージが提供されます。

サーバー側: CORS フレームワーク自体により、Web API のトレース機能を使用して、詳細なトレース メッセージが提供されます。ITraceWriter が Web API に登録されていれば、CORS フレームワークにより、選択されているポリシー プロバイダー、使用されているポリシー、および発行されている CORS HTTP ヘッダーについての情報を含むメッセージが発行されます。Web API トレースの詳細については、MSDN の Web API ドキュメントを参照してください。

強く求めらる機能

CORS はこれまでかなりの期間にわたって強く求められていた機能で、ついに Web API に組み込まれました。この記事は、CORS 自体の詳細に重点を置いていますが、この知識は CORS の実装とデバッグの際にきわめて重要です。この知識を基にすれば、アプリケーションで生成元をまたがる呼び出しを許可するため、Web API の CORS サポートを簡単に利用できるでしょう。

Brock Allen は、Microsoft .NET Framework、Web 開発、および Web ベース セキュリティを専門としているコンサルタントです。また、教育関連の会社 DevelopMentor のインストラクター、thinktecture GmbH & Co. KG の副主任コンサルタントであり、thinktecture オープンソース プロジェクトや ASP.NET プラットフォームに貢献しています。彼の連絡先は、Web サイト brockallen.com(英語)または電子メール brockallen@gmail.com(英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Huan Lin (マイクロソフト) に心より感謝いたします。
Yao Huang Lin (yaohuang@microsoft.com(英語のみ)) は、マイクロソフトの ASP.NET Web API チームのソフトウェア開発者です。彼は、ASP.NET、Windows Communication Foundation (WCF) や Windows Workflow Foundation (WF) を含め、.NET Framework の多くのコンポーネントに携わってきました。