Enable Cross-Origin Requests (CORS) in ASP.NET Core

By Mike Wasson, Shayne Boyer, and Tom Dykstra

Browser security prevents a web page from making requests to a different domain than the one that served the web page. This restriction is called the same-origin policy. The same-origin policy prevents a malicious site from reading sensitive data from another site. Sometimes, you might want to allow other sites make cross-origin requests to your app.

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. CORS is safer and more flexible than earlier techniques, such as JSONP. This topic shows how to enable CORS in an ASP.NET Core app.

Same origin

Two URLs have the same origin if they have identical schemes, hosts, and ports (RFC 6454).

These two URLs have the same origin:

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

These URLs have different origins than the previous two URLs:

  • https://example.net – Different domain
  • https://www.example.com/foo.html – Different subdomain
  • http://example.com/foo.html – Different scheme
  • https://example.com:9000/foo.html – Different port

Note

Internet Explorer doesn't consider the port when comparing origins.

Register CORS services

Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.AspNetCore.Cors package.

Reference the Microsoft.AspNetCore.All metapackage or add a package reference to the Microsoft.AspNetCore.Cors package.

Add a package reference to the Microsoft.AspNetCore.Cors package.

Call AddCors in Startup.ConfigureServices to add CORS services to the app's service container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
}

Enable CORS

After registering CORS services, use either of the following approaches to enable CORS in an ASP.NET Core app:

  • CORS Middleware – Apply CORS policies globally to the app via middleware.
  • CORS in MVC – Apply CORS policies per action or per controller. CORS Middleware isn't used.

Enable CORS with CORS Middleware

CORS Middleware handles cross-origin requests to the app. To enable CORS Middleware in the request processing pipeline, call the UseCors extension method in Startup.Configure.

CORS Middleware must precede any defined endpoints in your app where you want to support cross-origin requests (for example, before the call to UseMvc for MVC/Razor Pages Middleware).

A cross-origin policy can be specified when adding the CORS Middleware using the CorsPolicyBuilder class. There are two approaches for defining a CORS policy:

  • Call UseCors with a lambda:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        // Shows UseCors with CorsPolicyBuilder.
        app.UseCors(builder =>
           builder.WithOrigins("http://example.com"));
    
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    

    The lambda takes a CorsPolicyBuilder object. Configuration options, such as WithOrigins, are described later in this topic. In the preceding example, the policy allows cross-origin requests from https://example.com and no other origins.

    The URL must be specified without a trailing slash (/). If the URL terminates with /, the comparison returns false and no header is returned.

    CorsPolicyBuilder has a fluent API, so you can chain method calls:

    app.UseCors(builder =>
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader()
        );
    
  • Define one or more named CORS policies and select the policy by name at runtime. The following example adds a user-defined CORS policy named AllowSpecificOrigin. To select the policy, pass the name to UseCors:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("AllowSpecificOrigin",
                builder => builder.WithOrigins("http://example.com"));
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
        ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        // Shows UseCors with named policy.
        app.UseCors("AllowSpecificOrigin");
    
        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
    

Enable CORS in MVC

You can alternatively use MVC to apply specific CORS policies per action or per controller. When using MVC to enable CORS, the registered CORS services are used. The CORS Middleware isn't used.

Per action

To specify a CORS policy for a specific action, add the [EnableCors] attribute to the action. Specify the policy name.

[HttpGet]
[EnableCors("AllowSpecificOrigin")]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

Per controller

To specify the CORS policy for a specific controller, add the [EnableCors] attribute to the controller class. Specify the policy name.

[Route("api/[controller]")]
[EnableCors("AllowSpecificOrigin")]
public class ValuesController : ControllerBase

The precedence order is:

  1. action
  2. controller

Disable CORS

To disable CORS for a controller or action, use the [DisableCors] attribute:

[HttpGet("{id}")]
[DisableCors]
public string Get(int id)
{
    return "value";
}

CORS policy options

This section describes the various options that you can set in a CORS policy.

For some options, it may be helpful to read the How CORS works section first.

Set the allowed origins

The CORS middleware in ASP.NET Core MVC has a few ways to specify allowed origins:

  • WithOrigins: Allows specifying one or more URLs. The URL may include the scheme, host name, and port without any path information. For example, https://example.com. The URL must be specified without a trailing slash (/).
options.AddPolicy("AllowSpecificOrigins",
builder =>
{
    builder.WithOrigins("http://example.com", "http://www.contoso.com");
});
  • AllowAnyOrigin: Allows CORS requests from all origins with any scheme (http or https).
options.AddPolicy("AllowAllOrigins",
    builder =>
    {
        builder.AllowAnyOrigin();
    });

Consider carefully before allowing requests from any origin. Allowing requests from any origin means that any website can make cross-origin requests to your app.

Note

Specifying AllowAnyOrigin and AllowCredentials is an insecure configuration and can result in cross-site request forgery. The CORS service returns an invalid CORS response when an app is configured with the two.

Note

Specifying AllowAnyOrigin and AllowCredentials is an insecure configuration and can result in cross-site request forgery. Consider specifying an exact list of origins if your client needs to authorize to access server resources.

This setting affects preflight requests and the Access-Control-Allow-Origin header (described later in this topic).

// BEGIN10
options.AddPolicy("AllowSubdomain",
    builder =>
    {
        builder.SetIsOriginAllowedToAllowWildcardSubdomains("https://*.example.com");
    });
// END11

Set the allowed HTTP methods

To allow all HTTP methods, call AllowAnyMethod:

options.AddPolicy("AllowAllMethods",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyMethod();
    });

This setting affects preflight requests and the Access-Control-Allow-Methods header (described later in this topic).

Set the allowed request headers

To allow specific headers to be sent in a CORS request, called author request headers, call WithHeaders and specify the allowed headers:

options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

To allow all author request headers, call AllowAnyHeader:

options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

This setting affects preflight requests and the Access-Control-Request-Headers header (described later in this topic).

A CORS Middleware policy match to specific headers specified by WithHeaders is only possible when the headers sent in Access-Control-Request-Headers exactly match the headers stated in WithHeaders.

For instance, consider an app configured as follows:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware declines a preflight request with the following request header because Content-Language (HeaderNames.ContentLanguage) isn't listed in WithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

The app returns a 200 OK response but doesn't send the CORS headers back. Therefore, the browser doesn't attempt the cross-origin request.

CORS Middleware always allows four headers in the Access-Control-Request-Headers to be sent regardless of the values configured in CorsPolicy.Headers. This list of headers includes:

  • Accept
  • Accept-Language
  • Content-Language
  • Origin

For instance, consider an app configured as follows:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware responds successfully to a preflight request with the following request header because Content-Language is always whitelisted:

Access-Control-Request-Headers: Cache-Control, Content-Language

Set the exposed response headers

By default, the browser doesn't expose all of the response headers to the app. For more information, see W3C Cross-Origin Resource Sharing (Terminology): Simple Response Header.

The response headers that are available by default are:

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

The CORS specification calls these headers simple response headers. To make other headers available to the app, call WithExposedHeaders:

options.AddPolicy("ExposeResponseHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithExposedHeaders("x-custom-header");
    });

Credentials in cross-origin requests

Credentials require special handling in a CORS request. By default, the browser doesn't send credentials with a cross-origin request. Credentials include cookies and HTTP authentication schemes. To send credentials with a cross-origin request, the client must set XMLHttpRequest.withCredentials to true.

Using XMLHttpRequest directly:

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

In jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/home',
  xhrFields: {
    withCredentials: true
}

In addition, the server must allow the credentials. To allow cross-origin credentials, call AllowCredentials:

options.AddPolicy("AllowCredentials",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowCredentials();
    });

The HTTP response includes an Access-Control-Allow-Credentials header, which tells the browser that the server allows credentials for a cross-origin request.

If the browser sends credentials but the response doesn't include a valid Access-Control-Allow-Credentials header, the browser doesn't expose the response to the app, and the cross-origin request fails.

Be careful when allowing cross-origin credentials. A website at another domain can send a signed-in user's credentials to the app on the user's behalf without the user's knowledge.

The CORS specification also states that setting origins to "*" (all origins) is invalid if the Access-Control-Allow-Credentials header is present.

Preflight requests

For some CORS requests, the browser sends an additional request before making the actual request. This request is called a preflight request. The browser can skip the preflight request if the following conditions are true:

  • The request method is GET, HEAD, or POST.
  • The app doesn't set request headers other than Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID.
  • The Content-Type header, if set, has one of the following values:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

The rule on request headers set for the client request applies to headers that the app sets by calling setRequestHeader on the XMLHttpRequest object. The CORS specification calls these headers author request headers. The rule doesn't apply to headers the browser can set, such as User-Agent, Host, or Content-Length.

The following is an example of a preflight request:

OPTIONS https://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: https://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: The HTTP method that will be used for the actual request.
  • Access-Control-Request-Headers: A list of request headers that the app sets on the actual request. As stated earlier, this doesn't include headers that the browser sets, such as User-Agent.

A CORS preflight request might include an Access-Control-Request-Headers header, which indicates to the server the headers that are sent with the actual request.

To allow specific headers, call WithHeaders:

options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

To allow all author request headers, call AllowAnyHeader:

options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

Browsers aren't entirely consistent in how they set Access-Control-Request-Headers. If you set headers to anything other than "*" (or use AllowAnyHeader), you should include at least Accept, Content-Type, and Origin, plus any custom headers that you want to support.

The following is an example response to the preflight request (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: https://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 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.

If the preflight request is denied, the app returns a 200 OK response but doesn't send the CORS headers back. Therefore, the browser doesn't attempt the cross-origin request.

Set the preflight expiration time

The Access-Control-Max-Age header specifies how long the response to the preflight request can be cached. To set this header, call SetPreflightMaxAge:

options.AddPolicy("SetPreflightExpiration",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

How CORS works

This section describes what happens in a CORS request at the level of the HTTP messages. It's important to understand how CORS works so that the CORS policy can be configured correctly and debugged when unexpected behaviors occur.

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. Custom JavaScript code isn't required to enable CORS.

The following is an example of a cross-origin request. The Origin header provides the domain of the site that's making the request:

GET https://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: https://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: https://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

If the server allows the request, it sets the Access-Control-Allow-Origin header in the response. The value of this header either matches the Origin header from the request 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: https://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12

Test message

If the response doesn't include the Access-Control-Allow-Origin header, the cross-origin request fails. Specifically, the browser disallows the request. Even if the server returns a successful response, the browser doesn't make the response available to the client app.

Additional resources