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 のサポートについて説明します。 まず、Web API コントローラーをホストする "WebService" と WebService を呼び出す "WebClient" という 2 つの ASP.NET プロジェクトを作成します。 2 つのアプリケーションは異なるドメインでホストされるため、WebClient から WebService への AJAX 要求はクロスオリジン要求です。

Web サービスと Web クライアントを表示します

"同じ配信元" とは

2 つの URL が同じスキーム、ホスト、ポートを持つ場合、同じ配信元になります。 (RFC 6454)

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

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

これらの URL の配信元は、前の 2 つの URL とは異なります。

  • 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を使用したはじめに」を参照してください。

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

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

    Visual Studio の [新しい ASP.NET プロジェクト] ダイアログ

  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 ブラウザー

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

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

    Visual Studio の [新しい ASP.NET プロジェクト] ダイアログの MVC テンプレート

  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 サイトに発行します。

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

ブラウザーで

Note

Fiddler などのツールで HTTP トラフィックをwatchすると、ブラウザーが GET 要求を送信し、要求は成功したが、AJAX 呼び出しでエラーが返されることがわかります。 同じ配信元ポリシーでは、ブラウザーが要求を 送信 できないようにしないことを理解しておくことが重要です。 代わりに、アプリケーションに 応答が表示されなくなります。

Web 要求を示す Fiddler Web デバッガー

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] のパラメーターについて詳しく説明します。

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

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

成功したテスト メッセージを示す Web ブラウザー

CORS のしくみ

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

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 仕様では、これらの "author request headers" が呼び出されます)。この規則は、ユーザー エージェント、ホスト、コンテンツ長など、 ブラウザー で設定できるヘッダーには適用されません。

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

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-Headers ヘッダーが要求と共に送信され、OPTIONS ヘッダーが IIS を介してアプリに到達することをAccess-Control-Request-Method確認します。

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

<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] 属性を設定します。 次の例では、 メソッドに対してのみ CORS を GetItem 有効にします。

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() { ... }    
}

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

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

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

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

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

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

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

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • 更新日時
  • 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 の場合、配信元を "*" に設定することは無効であるとも示されています。

カスタム 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 のサポートが含まれています。