Enabling Cross Origin Requests (CORS) in ASP.NET Core

Introduction

Cross Origin Requests Sharing (CORS) is a W3C/WHATWG standard of allowing cross origin requests. It makes cross origin request handling much more simpler for Web APIs and services.

In this article we are going to see how CORS work and how it protects users from being manipulated. We’ll see how we can allow CORS in ASP.NET core web api for different origins.

What is CORS & What is Same-Origin policy?

Here’s an example: think about a website www.1234-earn-money-fast.com. A site which is specifically built to harm whoever opens it. A person sends you this website in email. You open the link and it fires an ajax request to www.paypal.com/transfer-all-money-to-some-account. Now all your paypal balance is transferred to another account. Without you even knowing it.

How CORS prevent this? Your web browser will always set an origin header whenever an ajax request is initiated. Origin header can not be manipulated by javascript. Its a restricted header and can only be set by the web browser. Now when ajax request is initiated, origin value will be set to ‘www.1234-earn-money-fast.com’. This is the origin from where this request was originated. Now Paypal will obviously deny the request since the origin is not into its allowed origins list. Thus, no request will be completed and no money will be transfered to any account. This is how CORS helps.

Allowing Trusted Origins On Server Side CORS Policy

Allowing origins on ASP.NET Web API 2 with a CORS policy is really simple. Add this in your Startup.cs file in ConfigureServices method before MVC add:

 services.AddCors();

This will AddCors support and now add this to Configure method:

app.UseCors(builder => builder
    .AllowAnyOrigin() // allow all origins
    .AllowAnyMethod() // allow all methods
    .AllowAnyHeader() // allow any header
    .AllowCredentials()); // allow credentials
app.UseMvc();

This will simply allow all origins to request your API endpoints. But if you want to implement a strict control over what origins can use your api endpoints then you can type a strict policy like this. Add the following custom policy to ConfigureServices method:

services.AddCors(o => o.AddPolicy("MyCORSPolicy", builder =>
 {
       builder
       .WithOrigins("http://localhost:4200") // allow this origin only
       .AllowAnyMethod() // allow all methods
       .AllowAnyHeader(); // allow any headers
 })); 

and now you can use your newly created policy like this:

app.UseCors("MyCORSPolicy");
app.UseMvc();

This will add your policy to CORS pipeline. You can also specify on your controller what CORS policy to implement like this:

[Route("api/[controller]")]
[EnableCors("MyCORSPolicy")]
public class AdminController : ControllerBase

Implementing custom Middleware for allow CORS for any origin

Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Having CORS errors for withCredentials include/true

What if you have no control over the withCredentials option? If withCredentials is set to true in the request. Server must respond with the same origin as present in request. It can not send back * as origin and credentials must be true as well from server side.

For example, if you’re working with ASP.NET Core 2 with @asp.net/signalr framework. Notice they have withCredentials set to true by default in their source code. So you can’t override this value. So we write a simple CORS middleware to handle this situation:

public class CorsOverride
{
  private readonly RequestDelegate _next;

  public CorsOverride(RequestDelegate next)
  {
    _next = next;
  }

  public async Task Invoke(HttpContext httpContext)
  {
    const string allowOriginHeaderName = "Access-Control-Allow-Origin";
    if (httpContext.Response.Headers.ContainsKey(allowOriginHeaderName))
    {
      httpContext.Response.Headers.Remove(allowOriginHeaderName);
    }

    httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
    httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "x-requested-with");

    if (httpContext.Request.Headers["Access-Control-Request-Method"].Count > 0)
    {
      foreach (var header in httpContext.Request.Headers["Access-Control-Request-Method"])
      {
        httpContext.Response.Headers.Add("Access-Control-Allow-Methods", header);
      }
    }
    else
    {
      httpContext.Response.Headers.Add("Access-Control-Allow-Methods", httpContext.Request.Method);
    }

    foreach (var origin in httpContext.Request.Headers.Where(h => h.Key == "Origin"))
    {
      httpContext.Response.Headers.Add(allowOriginHeaderName, origin.Value);
    }

    if (httpContext.Request.Method == "OPTIONS")
    {
      httpContext.Response.StatusCode = 200;
      await httpContext.Response.WriteAsync("");
    }
    else
    {
      await _next.Invoke(httpContext);
    }
  }
}

Notice this piece of code. We are looping here for origins from the request headers sent from the browser and simply setting the same value in the response header. This way we are returning same origin in response, hence withCredentials true and same origin both conditions are met.

foreach (var origin in httpContext.Request.Headers.Where(h => h.Key == "Origin"))
{
  httpContext.Response.Headers.Add(allowOriginHeaderName, origin.Value);
}

Conclusion

If you’re developing front end using Vue.js or React.js you’ll face this problem a lot since origins will never be same when developing the application. Sometimes even in production environment you may deploy app on different domains.

So this article covers how you can allow some origins, all origins or completely bypass origins by writing a custom CORS middleware

If you have any queries, do write below.

Thanks

1 Reply to “Enabling Cross Origin Requests (CORS) in ASP.NET Core”

Leave a Reply

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