Setting up and Configuring CORS in an ASP.NET Core API

In this detailed guide, let's talk about what is CORS and how do we enable CORS in ASP.NET Core with an illustrating example

Introduction

When developing client-server interactive applications where the Frontend sends a HTTP request to the Backend API for data, systems are designed such that the Frontend and Backend are deployed in separate domains to facilitate scaling independently.

It means that while the Frontend runs at myapp.com, the Backend may run at api.myapp.com or myappapi.com.

In either the case, there can be situations where the requests from the Frontend may fail with an error like below –

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

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

This is called a Cross-Origin Request issue or shortly, a CORS issue.

In this article, let’s deep dive into what CORS is and how we configure our AspNetCore application to enable CORS.

What is CORS?

Cross Origin Resource Sharing or simply called CORS is a mechanism that governs access of resources between two components over the 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.

Simply put, When a Backend resource receives an XHR Request from a client which is NOT from the same domain as itself, CORS issue occurs.

The solution – whitelist it.

What is a Preflight Request?

For every request made to an application from a client, the client first sends a “preflight” request to the Backend.

A Preflight request 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 a 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 a 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 resources to the client from that domain, else it means it is a denial.

For example – 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

If the Backend “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.

All the popular API frameworks have libraries in place to specify which “domains” are “allowed” for “CORS” access in the name of domain filters.

All requests which fall out of this 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.

How to enable CORS in ASP.NET Core API

Since ASP.NET Core applications are server applications which run on web servers, they are the Backend APIs to which the client applications make requests for data.

Hence we 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”.

ASP.NET CORE SDK provides CORS middleware using which we can configure and set CORS headers to allow/deny client requests.

You can set up CORS middleware to access control based on Domain, Method, Headers and Credentials.

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 of the generated response so as to indicate that the client is allowed for access.

First we create a CorsPolicy where we specify the access control. Then we specify the CORS middleware to use this CorsPolicy created.

In .NET 6, we create a CorsPolicy like below –

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAllCors", config =>
    {
        config.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    });
});

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

  1. Domain or Origin
  2. Method
  3. Headers

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

In .NET 6, We specify the CorsPolicy created above inside the UseCors middleware as below –

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseRouting();

app.UseCors("AllowAllCors");

How to enable CORS in .NET 6 API

Keep in mind that to successfully configure CORS in .NET 6, you must first create a CorsPolicy inside the AddCors() service and then refer that CorsPolicy inside the UseCors() middleware. Otherwise it doesn’t work.

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

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowCors", config =>
    {
        config.WithOrigins(builder.Configuration["AllowedOriginsList"]);
        config.WithMethods(builder.Configuration["AllowedMethodsList"]);
        config.WithHeaders(builder.Configuration["AllowedHeadersList"]);
    });
});

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 below.

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAllCors", config =>
    {
        config.AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    });
});

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

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

How to control access with CORS Headers

Which means that the request sent by the client from the domain “myapp.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 “myapp.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.

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowCorsMethod", config =>
    {
        config.AllowAnyMethod();
    });
});

or for a specified set of Methods,

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowCorsMethod", config =>
    {
        config.WithMethods(builder.Configuration["AllowedMethodsList"]);
    });
});

which ensures that preflight requests with only a 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.

builder.Services.AddCors(options =>
{
    options.AddPolicy(
        "AllowCorsMethod",
        config =>
        {
            config.AllowAnyHeader();
        }
    );
});

or for a specified set of Methods,

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowCors", config =>
    {
        config.WithHeaders(builder.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.

builder.Services.AddCors(options =>
{
    options.AddPolicy(
        "AllowAllCors",
        config =>
        {
            if (builder.Environment.IsDevelopment())
            {
                config.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
            }
            else
            {
         config.WithOrigins(builder.Configuration["AllowedOriginsList"]);
                config.WithMethods(builder.Configuration["AllowedMethodsList"]);
                config.WithHeaders(builder.Configuration["AllowedHeadersList"]);
            }
        }
    );
});

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.

To see CORS in a real-world example, you can check out ContainerNinja.CleanArchitecture – my .NET 6 Boilerplate solution which has many such features and libraries implemented and demonstrated.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

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