How to Setup CORS in ASP.NET Core API

When developing client-server interactive applications in which the client calls over the server API requesting for a resource (data) there can be situations where the requests or specifically the “preflight” requests fail from the client due to errors like:

No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'domain.com' is therefore not allowed access.

This is an indication of Cross-Origin Request issues or shortly called CORS issues. In this article, let’s talk about what CORS is and how do we configure our AspNetCore application to enable CORS.

What is CORS – Explained:

Cross Origin Resource Sharing or simply called CORS is a mechanism that governs access of resources between two components over web. It is a policy defined by web applications that specifies how a resource hosted under a domain can be accessed by another component out of its own domain over http. Generally, static assets such as images, CSS stylesheets, scripts and videos are allowed access across domains but resources which are requested by means of Ajax (asynchronous javascript and xml) requests are restricted by the CORS policy. In order to allow access requests from another domain, a web application needs to set a CORS policy that “allows” access to such requests and the same is sent over by means of response headers to the requesting parties.

Preflight requests and Cross Origin validation:

For every request made to an application from a client, the client first sends a “preflight” request which is a light-weight request sent to the resource owner (or the server) with a snapshot of what the actual request could look like. It contains information such as what headers that shall be sent, the method to be used and such. It is an OPTIONS request made to the server to check whether the server actually “allows” such request from the client if made.

For example, a preflight request to a GET request to the server can look like below:

OPTIONS /api/foo/all 
Access-Control-Request-Method: GET 
Access-Control-Request-Headers: origin, x-request-token
Origin: https://api.foo.com

When the server receives such as request, it responds back with either an “allow” or “deny” such request from the client of that domain. If it is an “allow” it means that the server is ready to serve resource to the client from that domain, else it means its a denial. In the below response, the server responds back that it “allows” the methods GET, POST, OPTIONS and DELETE for the client and so the client can go ahead and push the actual request.

HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: https://api.foo.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400

On the contrary, if the server “denies” the client request, it results in what we generally see a “preflight request failed” error. This is a standard case of what happens when the server isn’t configured with CORS for the client domain.

A standard API application has configurations to specify which “domains” are “allowed” for “CORS” access in the name of domain filters. All requests which fall out of these set of “allowed CORS domains” receive a preflight “deny” which means the clients can’t access the API resources from their domains. This way, CORS headers provide security to the API from unwanted resource accesses.

ASPHostPortal

Configuring CORS in ASP.NET Core API:

Since ASP.NET Core applications are server applications which run on webservers, its obvious that they play the role of an API “server” to which the client applications make request for data. Hence we would need to configure CORS headers for our ASP.NET Core APIs like any other applications to specify which “domains” are “allowed” and which are to be “restricted”.

To implement CORS on an aspnetcore API, we make use of CORS middleware provided by the dotnetcore sdk which works on the lines of a middleware. The role of this middleware is to check the preflight request origin and decide whether to “allow” or “deny”. If the domain is to be allowed access, it also sets up the CORS headers on top the generated response so as to indicate that the client is allowed for access.

To setup CORS, we can add the below Use<>() method in the Configure() method of the Startup class:

/* Startup class - Configure() method */

app.UseCors(builder =>
{
    // build a CORS specification
});

the variable builder is of type Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder, which provides options to give access to a cross-origin request by:

  1. Domain or Origin
  2. Method
  3. Headers

We can direct the aspnetcore CORS middleware to allow all the requests per the above constraint or add filter by the above criteria.

To restrict cross-origin access to a specific set of domains, we add the list of allowed origins to the builder as below:

app.UseCors(builder =>
{
    builder.WithOrigins(Configuration["AllowedOriginsList"]);
});

where AllowedOriginsList can be externalized onto the appsettings file as below:

// appsettings.json

{
    "AllowedOriginsList": [
        "http://localhost:4200",
        "http://localhost:8080",
        "http://localhost:80"
    ],
    "AllowedMethodsList": [
        "GET",
        "POST",
        "DELETE",
        "OPTIONS"
    ],
    "AllowedHeadersList": [
        "X-Request-Token",
        "Accept",
        "Content-Type",
        "Authorization"
    ]
}

alternatively, we can allow requests from any cross-origin domain as:

app.UseCors(builder =>
{
    builder.AllowAnyOrigin();
});

The error mentioned at the beginning of this article looked like:

No 'Access-Control-Allow-Origin' header is present on the requested resource.

Which means that the request sent by the client from the domain “domain.com” is not a domain allowed by the CORS middleware of the server, hence the server “denies” access to the request. To solve this, we can either add “domain.com” to the list of “allowed domains” we’re maintaining in our appsettings and feeding the CORS middleware or we can just set AllowAnyOrigin() on the builder (latter is not a recommended option for production level APIs though).

Similarly, we can either “restrict” requests on specific Method or Headers or we can allow them all.

app.UseCors(builder =>
{
    builder.AllowAnyMethod();
});

or for a specified set of Methods,

app.UseCors(builder =>
{
    builder.AllowAnyMethod(_configuration.AllowedMethodsList)
});

which ensures that preflight requests with only configured set of methods are “allowed” or otherwise denied.

Access-Control-Request-Method: GET

Similar is the case to specifically “allow” requests with certain headers or allow them all.

app.UseCors(builder =>
{
    builder.AllowAnyHeader();
});

or for a specified set of Methods,

app.UseCors(builder =>
{
    builder.WithHeaders(_configuration.AllowedHeadersList)
});

Which results in an Access-Control header,

Access-Control-Allow-Headers: origin, x-request-token

or for the server to allow any header,

Access-Control-Allow-Headers: *

When these configurations are in place, the above Access-Control headers are generated within the server and are validated against the headers being sent by the client in the preflight request. When the request satisfies the Access-Control specifications, the client is indicated of success with a successful response from the server as shown before.

Combining things together, we make our CORS middleware look like below:

app.UseCors(builder =>
{
    if (isAllowAllCrossOrigins)
    {
        builder
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowAnyOrigin();
    }
    else
    {
        builder.WithOrigins(_configuration.AllowedOriginsList)
            .WithHeaders(_configuration.AllowedHeadersList)
            .WithMethods(_configuration.AllowedMethodsList);
    }
});

Where _configuration refers to any customized ConfigurationManager service injected into the Configure() method. This adds a layer of ease and control over the Configuration object and allows us to be more conditional while adding CORS layer. Once these configurations are set, we can have our AspNetCore API allow clients from any (or configured list of) domains with methods and headers to access the data and resources.

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *