Language

Enabling Cross-Origin Requests in ASP.NET Web API

By Mike Wasson|
This tutorial shows how to support Cross-Origin Resource Sharing (CORS) in ASP.NET Web API.

Introduction

Browser security prevents a web page from making AJAX requests to another domain. This restriction is called the same-origin policy. However, sometimes you might want to let other sites call your web API.

Cross Origin Resource Sharing (CORS) is a W3C standard that allows a server to relax the same-origin policy. Using CORS, a server can explicitly allow some cross-origin requests while rejecting others.

This tutorial demonstrates the new CORS support in ASP.NET Web API. We’ll start by creating two ASP.NET projects – one called “WebService”, which hosts a Web API controller, and the other called “WebClient”, which calls WebService. Because the two applications are hosted at different domains, an AJAX request from WebClient to WebService is a cross-origin request.

Prerequisites

Visual Studio 2013 Preview or Visual Studio Express 2013 Preview for Web

Create the Web API Project

Open Visual Studio. Click New Project in the Start page or in the File menu.

In the New Project dialog, click Web in the left pane and ASP.NET Web Application in the middle pane. Name the project "WebService".

In the New ASP.NET Project dialog, select the Empty project template. Under “Add folders and core references for”, select the Web API checkbox. Click Create Project.

Add a Web API Controller

In Solution Explorer, right-click the Controllers folder. Select Add, then select Scaffold.

In the Add Scaffold dialog, select “Web API 2 Controller - Empty.” (You might need to scroll down to see this option.)

In the Add Controller dialog, name the controller “TestController”. Click Add.

Replace the boilerplate code with the following:

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")
            };
        }
    }
}

Now deploy this application to a server or VM. (For the screenshots in this tutorial, I deployed to Windows Azure Web Sites.)

To verify that the web API is working, navigate to http://hostname/api/test/, where hostname is the domain where you deployed the application. You should see the response text, "GET: Test Message".

Create the Client Application

In Solution Explorer, right-click the solution. Select Add and then select New Project.

In the Add New Project dialog, select ASP.NET Web Application, as before. Name the project "WebClient". Click OK.

In the New ASP.NET Project dialog, select the MVC project template. Optionally, click Change Authentication and select the No Authentication option. Click Create Project.

In Solution Explorer, expand the WebClient project and open the file Views/Home/Index.cshtml. Replace the code in this file with the following:

<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>
    var serviceUrl = 'http://myservice.azurewebsites.net/api/test'; // Replace with your URI.

    function sendRequest() {
        var method = $('#method').val();

        $.ajax({
            type: method,
            url: serviceUrl
        }).done(function (data) {
            $('#value1').text(data);
        }).error(function (jqXHR, textStatus, errorThrown) {
            $('#value1').text(jqXHR.responseText || textStatus);
        });
    }
</script>
}

For the serviceUrl variable, use the URI of the WebService application.

Now publish the WebClient project to another domain.

When the “Try It” button is clicked, the page submits an AJAX request to the WebService application, using the HTTP method listed in the dropdown box (either GET, POST, or PUT). This will let us examine various types of CORS request. Right now, the WebService application does not support cross-origin requests, so if you click the button, you will get an error.

If you watch the HTTP traffic in Fiddler or in the F12 developer tools, you will see that the browser does send the GET request. But in the application, the AJAX call returns an error. It’s important to understand that same-origin policy does not prevent the browser from sending the request. Instead, it prevents a cross-domain application from seeing the response.

Enable CORS in Web API

To enable CORS in Web API, use the Microsoft.AspNet.WebApi.Cors package, which is available on NuGet.

In Visual Studio, from the Tools menu, select Library Package Manager, then select Package Manager Console. In the Package Manager Console window, type the following command:

Install-Package Microsoft.AspNet.WebApi.Cors -pre -project WebService

This installs the CORS package, along with any dependencies, into the WebService project.

In Solution Explorer, expand the WebService project. Open the file App_Start/WebApiConfig.cs. Add the following code to the WebApiConfig.Register method.

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 }
            );
        }
    }
}

Next, add the [EnableCors] attribute to the TestController class, as follows"

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

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

For the origins parameter, use the URI where you deployed the WebClient application. This allows cross-domain requests from WebClient. I’ll describe the parameters for [EnableCors] in more detail later in this topic.

Deploy the updated WebService application. Now the AJAX request from WebClient should succeed:

The POST and PUT methods are also allowed:

Scope Rules for [EnableCors]

You can enable CORS per action, per controller, or globally for all Web API controllers in your application.

Per Action

To enable CORS for a single action, set the [EnableCors] attribute on the action method. The following example enables CORS for the GetItem method only.

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

Per Controller

If you set [EnableCors] on the controller class, it applies to all the actions on the controller. Then to disable CORS for an action, set the [DisableCors] attribute on the action. The following example enables CORS for all of the controller methods except PutItem.

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

Globally

To enable CORS for all Web API controllers in your application, pass an EnableCorsAttribute instance to the EnableCors method:

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

If you apply the attribute at more than one scope, the order of precedence is:

  1. Action
  2. Controller
  3. Global

How It Works

This section describes how a CORS request is actually performed, at the level of the HTTP messages. It’s important to understand how CORS works, so that you can configure the [EnableCors] attribute correctly, and to troubleshoot if things don’t work as you expect.

The CORS specification introduces several new HTTP headers that enable cross-origin requests. If a browser supports CORS, it sets these headers automatically for cross-origin requests; you don’t need to do anything special in your JavaScript code.

Here is an example of a cross-origin request:

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

The browser includes an “Origin” header that gives the domain of the site that is making the request. If the server allows the request, it sets the Access-Control-Allow-Origin header. The value of Access-Control-Allow-Origin either matches the Origin header, or is the wildcard value “*”, meaning that any origin is allowed.

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

If the response does not include the Access-Control-Allow-Origin header, the AJAX request fails. Specifically, the browser disallows the request. Even if the server returns a successful response, the browser does not make the response available to the client application.

Preflight Requests

For some CORS requests, the browser sends an additional request, called a “preflight request”, before it sends the actual request for the resource.

The browser can skip the preflight request if the following conditions are true:

  • The request method is GET, HEAD, or POST, and
  • The application does not set any request headers other than Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID, and
  • The Content-Type header (if set) is one of the following:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

The rule about request headers applies only to headers that the application sets, by calling setRequestHeader on the XMLHttpRequest object. (The CORS specification calls these “author request headers”.) The rule does not apply to headers set by the browser, such as User-Agent, Host, or Content-Length.

Here is an example of a preflight request:

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

The pre-flight request uses the HTTP OPTIONS method. It includes two special headers:

  • Access-Control-Request-Method: Specifies the HTTP method for the actual request.
  • Access-Control-Request-Headers: A list of request headers that the application has set for the actual request.

Here is an example response, assuming that the server allows the request:

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

The response includes an Access-Control-Allow-Methods header that lists the allowed methods, and optionally an Access-Control-Allow-Headers header, which lists the allowed headers. If the preflight request succeeds, the browser sends the actual request, as described earlier.

Specify the Allowed Origins

The origins parameter of the [EnableCors] attribute specifies which domains are allowed to access the resource. The value is a comma-separated list of the allowed domains.

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

You can also use the wildcard value “*” to allow requests from any domain.

Consider carefully before allowing requests from any origin. It means that literally any web page can make AJAX calls to your web API.

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

Specify the Allowed HTTP Methods

The methods parameter of the [EnableCors] attribute specifies which HTTP methods are allowed to access the resource. To allow all methods, use the wildcard value “*”. The following example allows only GET and POST requests.

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

Specify the Allowed Headers

Request Headers

Earlier I described how a preflight request might include an Access-Control-Request-Headers header, listing the HTTP headers set by the application (the so-called “author request headers”). The headers parameter of the [EnableCors] attribute specifies which author request headers are allowed. To allow any headers, set headers to “*”. To whitelist specific headers, set headers to a comma-separated list of the allowed headers:

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

However, browsers are not entirely consistent in how they set Access-Control-Request-Headers. For example, Chrome currently includes “origin”; while FireFox does not include standard headers such as “Accept”, even when the application sets them in script.

If you set headers to anything other than “*”, you should include at least “accept”, “content-type”, and “origin”, plus any custom headers that you want to support.

Response Headers

By default, the browser does not expose all of the response headers to the application. The response headers that are available by default are:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

The CORS spec calls these simple response headers. To make other headers available to the application, set the exposedHeaders parameter of [EnableCors].

In the following example, the controller’s Get method sets a custom header named ‘X-Custom-Header’. By default, the browser will not expose this header in a cross-origin request. To make the header available, we include ‘X-Custom-Header’ in exposedHeaders.

[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;
    }
}

Here is an example response from the server:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Expires: -1
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-Custom-Header: hello
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: X-Custom-Header
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 07 Jun 2013 18:55:39 GMT
Content-Length: 17

GET: Test message

Credentials

Credentials require special handling in a CORS request. By default, the browser does not send any credentials with a cross-origin request. (Credentials include cookies as well as HTTP authentication.) To send credentials with a cross-origin request, set the withCredentials property on the XMLHttpRequest object to true.

Using XMLHttpRequest directly:

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

In jQuery:

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

In addition, the server must allow the credentials. To allow cross-origin credentials in Web API, set the SupportsCredentials property to true on the [EnableCors] attribute:

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

If this property is true, the HTTP response will include an Access-Control-Allow-Credentials header. This header tells the browser that the server allows credentials for a cross-origin request.

If the browser sends credentials, but the response does not include a valid Access-Control-Allow-Credentials header, the browser will not expose the response to the application, and the AJAX request fails.

Be very careful about setting SupportsCredentials to true, because it means a website at another domain can send a logged-in user’s credentials to your Web API on the user’s behalf, without the user necessarily being aware. Also, note that according to the CORS spec, setting origins to "*" is not valid if SupportsCredentials is true.

Custom CORS Policy Providers

The [EnableCors] attribute implements the ICorsPolicyProvider interface. You can provide your own implementation by creating a class that derives from Attribute and implements ICorsProlicyProvider.

[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);
    }
}

Now you can apply the attribute any place that you would put [EnableCors].

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

For example, a custom CORS policy provider could read the settings from a configuration file.

As an alternative to using attributes, you can register an ICorsPolicyProviderFactory object that creates ICorsPolicyProvider objects.

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

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

To set the ICorsPolicyProviderFactory, call the SetCorsPolicyProviderFactory extension method at startup, as follows:

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

        // ...
    }
}

Summary

With CORS, you can allow cross-origin requests in a way that is safer and more flexible than earlier techniques such as JSONP. The Microsoft.AspNet.WebApi.Cors library lets you support CORS in your Web API application, simply by decorating your controller with the [EnableCors] attribute. For even more control, you can implement a custom CORS policy provider.

Author Information

Mike Wasson

Mike Wasson – Mike Wasson is a programmer-writer at Microsoft.