ASP.NET Web API 2 でクロスオリジン要求を有効にする

作成者: Mike Wasson

このコンテンツは、以前のバージョンの .NET 用です。 新しい開発では、ASP.NET Core を使用する必要があります。 ASP.NET Coreでの Web API とクロスオリジン要求 (CORS) の使用の詳細については、次を参照してください。

ブラウザーのセキュリティ機能により、Web ページでは AJAX 要求を別のドメインに送信することはできません。 この制限は、同一オリジン ポリシーと呼ばれ、悪意のあるサイトが、別のサイトから機密データを読み取れないようにします。 ただし、他のサイトから Web API を呼び出したほうが良い場合もあります。

クロスオリジン リソース共有 (CORS) はW3C 標準で、サーバーによる同一オリジン ポリシーの緩和を許可します。 CORS を使用することで、サーバーが一部のクロス オリジン要求を、その他の要求を拒否しながら、明示的に許可することができます。 CORS は、JSONP など、以前の手法に比べて安全性と柔軟性が向上しています。 このチュートリアルでは、Web API アプリケーションで CORS を有効にする方法について説明します。

このチュートリアルで使用されるソフトウェア

はじめに

このチュートリアルでは、ASP.NET Web API での CORS のサポートについて説明します。 最初に、2 つの ASP.NET プロジェクトを作成します。1 つは Web API コントローラーをホストする "WebService" と呼ばれるプロジェクトで、もう 1 つは WebService を呼び出す "WebClient" と呼ばれるプロジェクトです。 2 つのアプリケーションは異なるドメインでホストされるため、WebClient から WebService への AJAX 要求はクロスオリジン要求です。

Shows web service and web client

"同じオリジン" とは

2 つの URL が同じスキーム、ホスト、ポートを持つ場合、これらは同じオリジンを持ちます。 (RFC 6454)

次の 2 つの URL は同じオリジンです。

  • http://example.com/foo.html
  • http://example.com/bar.html

以下の URL は、前の 2 つとは異なる origin を持ちます。

  • http://example.net - 異なるドメイン
  • http://example.com:9000/foo.html - 異なるポート
  • https://example.com/foo.html - 異なるスキーム
  • http://www.example.com/foo.html - 異なるサブドメイン

Note

インターネット エクスプローラーでは、オリジンを比較する際にポートは考慮されません。

WebService プロジェクトを作成する

Note

このセクションでは、Web API プロジェクトの作成方法を既に把握していることを前提としています。 把握してない場合は、「ASP.NET Web API 2 の概要」を参照してください。

  1. Visual Studio を起動し、新しい ASP.NET Web アプリケーション (.NET Framework) プロジェクトを作成します。

  2. [新しい ASP.NET Web アプリケーション] ダイアログで、[空] プロジェクト テンプレートを選択します。 [フォルダーとコア参照の追加対象] で、[Web API] チェックボックスをオンにします。

    New ASP.NET project dialog in Visual Studio

  3. 次のコードを使用して TestController という名前の Web API コントローラーを追加します。

    using System.Net.Http;
    using System.Web.Http;
    
    namespace WebService.Controllers
    {
        public class TestController : ApiController
        {
            public HttpResponseMessage Get()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("GET: Test message")
                };
            }
    
            public HttpResponseMessage Post()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("POST: Test message")
                };
            }
    
            public HttpResponseMessage Put()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("PUT: Test message")
                };
            }
        }
    }
    
  4. アプリケーションはローカルで実行することも、Azure にデプロイすることもできます。 (このチュートリアルのスクリーンショットでは、アプリは Azure App Service Web Apps にデプロイされます。)Web API が動作していることを確認するには、http://hostname/api/test/ に移動します。この場合、hostname は、アプリケーションをデプロイしたドメインです。 応答テキスト "GET: Test Message" が表示されます。

    Web browser showing test message

WebClient プロジェクトを作成する

  1. 別の ASP.NET Web アプリケーション (.NET Framework) プロジェクトを作成し、MVC プロジェクト テンプレートを選択します。 必要に応じて、[認証の変更]>[認証なし] を選択します。 このプロジェクトでは、認証は必要ありません。

    MVC template in New ASP.NET Project dialog in Visual Studio

  2. ソリューション エクスプローラーで、Views/Home/Index.cshtml ファイルを開きます。 このファイル内のコードを以下に置き換えます。

    <div>
        <select id="method">
            <option value="get">GET</option>
            <option value="post">POST</option>
            <option value="put">PUT</option>
        </select>
        <input type="button" value="Try it" onclick="sendRequest()" />
        <span id='value1'>(Result)</span>
    </div>
    
    @section scripts {
    <script>
        // TODO: Replace with the URL of your WebService app
        var serviceUrl = 'http://mywebservice/api/test'; 
    
        function sendRequest() {
            var method = $('#method').val();
    
            $.ajax({
                type: method,
                url: serviceUrl
            }).done(function (data) {
                $('#value1').text(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                $('#value1').text(jqXHR.responseText || textStatus);
            });
        }
    </script>
    }
    

    serviceUrl 変数については、WebService アプリの URI を使用します。

  3. WebClient アプリをローカルで実行するか、別の Web サイトに公開します。

[試してみる] ボタンをクリックすると、ドロップダウン ボックスに一覧表示されている HTTP メソッド (GET、POST、または PUT) を使用して、AJAX 要求が WebService アプリに送信されます。 これにより、さまざまなクロスオリジン要求を調べることができます。 現在、WebService アプリは CORS をサポートしていないため、このボタンをクリックするとエラーが発生します。

'Try it' error in browser

Note

Fiddler などのツールで HTTP トラフィックを監視する場合、ブラウザーが GET 要求を送信し、この要求が成功することを確認できますが、AJAX 呼び出しではエラーが返されます。 同じオリジン ポリシーによってブラウザーが要求を送信することが阻止されないことを理解することが重要です。 代わりに、アプリケーションが応答を確認することが阻止されます。

Fiddler web debugger showing web requests

CORS を有効にする

次に、WebService アプリで CORS を有効にしましょう。 最初に、CORS NuGet パッケージを追加します。 Visual Studio の [ツール] メニューで、[NuGet パッケージ マネージャー]を選択し、[パッケージ マネージャー コンソール] を選択します。 [パッケージ マネージャー コンソール] ウィンドウで、次のコマンドを入力します。

Install-Package Microsoft.AspNet.WebApi.Cors

このコマンドにより、最新のパッケージがインストールされ、コア Web API ライブラリを含むすべての依存関係が更新されます。 特定のバージョンをターゲットにするには、-Version フラグを使用します。 CORS パッケージには、Web API 2.0 以降が必要です。

ファイル App_Start/WebApiConfig.cs を開きます。 WebApiConfig.Register メソッドに次のコードを追加します。

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

次に、[EnableCors] 属性を TestController クラスに追加します。

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

origins パラメータについては、WebClient アプリケーションをデプロイした URI を使用します。 これにより、WebClient からのクロスオリジン要求が可能になりますが、その他のクロスドメイン要求はすべて許可されません。 後で、[EnableCors] のパラメータについて詳しく説明します。

originsの URL の末尾にスラッシュを含めないでください。

更新された WebService アプリケーションを再デプロイします。 WebClient を更新する必要はありません。 これで、WebClient からの AJAX 要求が成功するようになります。 GET、PUT、POST メソッドはすべて許可されます。

Web browser showing successful test message

CORS のしくみ

このセクションでは、HTTP メッセージのレベルでの CORS 要求の処理について説明します。 CORS のしくみを理解することで、[EnableCors] 属性を正しく構成し、想定どおりに動作しない場合にトラブルシューティングを行えるようにすることが重要です。

CORS の仕様では、クロスオリジン要求を可能にするいくつかの新しい HTTP ヘッダーが導入されました。 ブラウザーで CORS がサポートされている場合、クロスオリジン要求に対してこれらのヘッダーが自動的に設定されます。JavaScript コードで特別な操作を行う必要はありません。

クロスオリジン要求の例を次に示します。 "Origin" ヘッダーは、要求を行っているサイトのドメインを提供します。

GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

サーバーによって要求が許可される場合、Access-Control-Allow-Origin ヘッダーが設定されます。 このヘッダーの値は、Origin ヘッダーと一致するか、ワイルドカード値 "*" (任意のオリジンが許可されることを意味します) です。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17

GET: Test message

応答に Access-Control-Allow-Origin ヘッダーが含まれていない場合、AJAX 要求は失敗します。 具体的には、ブラウザーで要求が許可されません。 サーバーから正常応答が返された場合でも、クライアント アプリケーションはブラウザーで応答を使用できません。

プレフライト要求

一部の CORS 要求の場合、ブラウザーは、"プレフライト要求" と呼ばれる追加の要求を送信した後、リソースの実際の要求を送信します。

次の条件がすべて当てはまる場合、ブラウザーはプレフライト要求をスキップできます。

  • 要求メソッドが GET、HEAD、または POST である。かつ

  • アプリケーションが Accept、Accept-Language、Content-Language、Content-Type、または Last-Event-ID 以外の要求ヘッダーを設定しない。かつ

  • Content-Type ヘッダー (設定されている場合) が次のいずれかである。

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

要求ヘッダーに関する規則は、アプリケーションが XMLHttpRequest オブジェクトで setRequestHeader を呼び出すことで設定するヘッダーに適用されます。 (CORS の仕様では、これらは "作成者要求ヘッダー" と呼ばれます。)この規則は、ブラウザーが設定できるヘッダー (User-Agent、Host、または Content-Length など) には適用されません。

プレフライト要求の例を次に示します。

OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

プレフライト要求では HTTP OPTIONS メソッドが使用されます。 これには、次の 2 つの特別なヘッダーが含まれています。

  • Access-Control-Request-Method: 実際の要求に使用される HTTP メソッド。
  • Access-Control-Request-Headers: アプリケーションが実際の要求に設定する要求ヘッダーのリスト。 (ここでも、ブラウザーが設定するヘッダーは含まれていません。)

サーバーが要求を許可することを前提した場合の応答の例を次に示します。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT

応答には、許可されたメソッドが一覧表示される Access-Control-Allow-Methods ヘッダーと、必要に応じて、許可されたヘッダーが一覧表示される Access-Control-Allow-Headers ヘッダーが含まれます。 プレフライト要求が成功した場合、ブラウザーは前述のように実際の要求を送信します。

プレフライト OPTIONS 要求を使用してエンドポイントをテストするために一般的に使用されるツール (FiddlerPostman など) は、既定では必要な OPTIONS ヘッダーを送信しません。 この要求と共に Access-Control-Request-Method および Access-Control-Request-Headers ヘッダーが送信され、OPTIONS ヘッダーが IIS 経由でアプリに到達することを確認してください。

ASP.NET アプリが OPTION 要求を受信して処理できるように IIS を構成するには、<system.webServer><handlers>セクション内のアプリの web.config ファイルに次の構成を追加します。

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

OPTIONSVerbHandler を削除すると、IIS が OPTIONS 要求を処理できなくなります。 ExtensionlessUrlHandler-Integrated-4.0 を置き換えると、OPTIONS 要求がアプリに到達できるようになります。なぜなら、既定のモジュール登録では、拡張子なしの URL を使用した GET、HEAD、POST、DEBUG 要求のみが許可されるからです。

[EnableCors] のスコープ規則

CORS は、アクションごと、コントローラーごと、またはアプリケーション内のすべての Web API コントローラーに対してグローバルに有効にすることができます。

アクションごと

1 つのアクションに対して CORS を有効にするには、アクション メソッドに [EnableCors] 属性を設定します。 次の例では、GetItem メソッドに対してのみ CORS を有効にします。

public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

コントローラーごと

コントローラー クラスで [EnableCors] を設定すると、コントローラー上のすべてのアクションに適用されます。 アクションの CORS を無効にするには、アクションに [DisableCors] 属性を追加します。 次の例では、PutItem を除くすべてのメソッドに対して CORS を有効にします。

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

グローバル

アプリケーション内のすべての Web API コントローラーに対して CORS を有効にするには、EnableCorsAttribute インスタンスを EnableCors メソッドに渡します。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

複数のスコープで属性を設定した場合、優先順位は次のようになります。

  1. アクション
  2. コントローラー
  3. グローバル

許可されるオリジンを設定する

[EnableCors] 属性の origins パラメータは、リソースへのアクセスを許可するオリジンを指定します。 この値は、許可されるオリジンのコンマ区切りのリストです。

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
    headers: "*", methods: "*")]

また、ワイルドカード値 "*" を使用して、任意のオリジンからの要求を許可することもできます。

慎重に検討してから、任意のオリジンからの要求を許可してください。 これは文字どおり、任意の Web サイトが Web API に対して AJAX 呼び出しを行うことができることを意味します。

// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]

許可される HTTP メソッドを設定する

[EnableCors] 属性の methods パラメータは、リソースへのアクセスを許可する HTTP メソッドを指定します。 すべてのメソッドを許可するには、ワイルドカード値 "*" を使用します。 次の例では、GET 要求と POST 要求のみを許可します。

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

許可される要求ヘッダーを設定する

この記事では先に、アプリケーションによって設定される HTTP ヘッダー (いわゆる "作成者要求ヘッダー") を一覧表示して、プレフライト要求に Access-Control-Request-Headers ヘッダーがどのように含まれるかについて説明しました。 [EnableCors] 属性の headers パラメータは、許可される作成者要求ヘッダーを指定します。 任意のヘッダーを許可するには、headers を "*" に設定します。 特定のヘッダーを許可するには、headers を、許可されるヘッダーのコンマ区切りリストに設定します。

[EnableCors(origins: "http://example.com", 
    headers: "accept,content-type,origin,x-my-header", methods: "*")]

ただし、ブラウザーは Access-Control-Request-Headers の設定方法に完全に一貫しているわけではありません。 たとえば、現在、Chrome には "origin" が含まれています。 FireFox では、"Accept" などの標準ヘッダーは含まれていません。これは、アプリケーションがスクリプトでこれらのヘッダーを設定するとしてもです。

headers を "*" 以外に設定する場合、少なくとも "accept"、"content-type"、"origin" に加えて、サポートするカスタム ヘッダーを含める必要があります。

許可される応答ヘッダーを設定する

既定では、ブラウザーがすべての応答ヘッダーをアプリケーションに公開するわけではありません。 既定で使用できる応答ヘッダーは次のとおりです。

  • キャッシュ-コントロール
  • コンテンツ言語
  • コンテンツタイプ
  • 有効期限
  • 更新日時
  • Pragma

CORS 仕様では、これらを単純な応答ヘッダーと呼んでいます。 アプリケーションでその他のヘッダーを使用できるようにするには、[EnableCors]exposedHeaders パラメータを設定します。

次の例では、コントローラーの Get メソッドによって "X-Custom-Header" という名前のカスタム ヘッダーが設定されます。 既定では、ブラウザーはクロスオリジン要求でこのヘッダーを公開しません。 このヘッダーを使用できるようにするには、exposedHeaders に "X-Custom-Header" を含めます。

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

クロスオリジン要求に資格情報を渡す

資格情報は、CORS 要求で特別に処理する必要があります。 既定では、ブラウザーはクロス オリジン要求と共に資格情報を送信しません。 資格情報には、Cookie と HTTP 認証スキームが含まれます。 クロスオリジン要求と共に資格情報を送信するには、クライアントが XMLHttpRequest.withCredentials を true に設定する必要があります。

XMLHttpRequest の直接使用:

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

jQuery 内:

$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

また、サーバーが資格情報を許可する必要があります。 Web API でクロスオリジン資格情報を許可するには、[EnableCors] 属性で SupportsCredentials プロパティを true に設定します。

[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
    methods: "*", SupportsCredentials = true)]

このプロパティが true である場合、HTTP 応答には Access-Control-Allow-Credentials ヘッダーが含まれます。 このヘッダーにより、サーバーでクロスオリジン要求の資格情報が許可されることがブラウザーに伝えられます。

ブラウザーが資格情報を送信しても、応答に有効な Access-Control-Allow-Credentials ヘッダーが含まれていない場合は、ブラウザーからアプリケーションに応答は公開されず、AJAX 要求は失敗します。

SupportsCredentials を true に設定すると、別のドメインにある Web サイトが、ログインしたユーザーの資格情報を、そのユーザーが知らないところでユーザーの代わりに Web API に送信できるようになるため、注意が必要です。 CORS 仕様では、SupportsCredentials が true である場合、origins を "*" に設定しても無効であることが示されています。

カスタム CORS ポリシー プロバイダー

[EnableCors] 属性は、ICorsPolicyProvider インターフェイスを実装します。 Attribute から派生し、ICorsPolicyProvider を実装するクラスを作成することで、独自の実装を提供できます。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

これで、[EnableCors] を配置する任意の場所に属性を適用できるようになりました。

[MyCorsPolicy]
public class TestController : ApiController
{
    .. //

たとえば、カスタム CORS ポリシー プロバイダーは、構成ファイルから設定を読み取ることができます。

属性を使用する代わりに、ICorsPolicyProvider オブジェクトを作成する ICorsPolicyProviderFactory オブジェクトを登録できます。

public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
    ICorsPolicyProvider _provider = new MyCorsPolicyProvider();

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        return _provider;
    }
}

ICorsPolicyProviderFactory を設定するには、次のように起動時に SetCorsPolicyProviderFactory 拡張メソッドを呼び出します。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
        config.EnableCors();

        // ...
    }
}

ブラウザーのサポート

Web API CORS パッケージは、サーバー側のテクノロジです。 ユーザーのブラウザーも CORS をサポートする必要があります。 幸い、すべての主要なブラウザーの現在のバージョンには CORS のサポートが含まれています。