In this article, we will present one of the problems that users faced many years ago when using Ajax requests, which is the same-origin policy. The solution came via a protocol called Cross-Origin Resource Sharing (CORS), so that browsers could accept and process requests coming from other origins than the one of the requester. Read on to discover what the same-origin policy is, how CORS works to circumvent this policy, and examples of how to implement it in an application built with ASP.NET Core 3.1.
User agents (UAs) and the Same-Origin policy
User agents (clients in a network protocol used in communications within a client–server distributed computing system) group URIs together into protection domains called “origins.” Roughly speaking, two URIs are part of the same origin (i.e., represent the same principal) if they have the same scheme, host, and port. User agents commonly apply same-origin restrictions to network requests. These restrictions prevent a client-side Web application running from one origin from obtaining data retrieved from another origin, and also limit unsafe HTTP requests that can be automatically launched toward destinations that differ from the running application’s origin.
Defining CORS
Cross-Origin Resource Sharing (CORS) is a W3C Working Draft that defines how the browser and server must communicate when accessing sources across origins. The basic idea behind CORS is to use custom HTTP headers to allow both the browser and the server to know enough about each other to determine if the request or response should succeed or fail.
Exposing APIs to foreign origins is dangerous and should be done only with great care because doing so exposes them to potential attackers.
Example:
A CORS policy is a set of HTTP response headers. A basic CORS policy can look like this:
Access-Control-Allow-Origin: https://foo.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: Content-Type
This policy states that the origin https://foo.example.com is allowed to make a GET request, cookies may be included and we are allowed to send the Content-Type header.
For HTTP request methods that can cause side-effects on server data (in particular, HTTP methods other than GET, or POST with certain MIME types), the specification mandates that browsers “preflight” the request, soliciting supported methods from the server with the HTTP OPTIONS request method, and then, upon “approval” from the server, sending the actual request.
CORS failures result in errors, but for security reasons, specifics about the error are not available to JavaScript. All the code knows is that an error occurred. The only way to determine what specifically went wrong is to look at the browser’s console for details.
ASP.NET Core 3.1 demo:
One of the best ways to illustrate a situation where CORS is involved in .NET Core 3.1 is to have a solution consisting in a user application that makes a call to a web API via an Ajax call from a JavaScript file.
So go ahead and create two separate projects, one containing the client-side application, and one containing the server-side API. In the client-side code then place a button that, when it’s clicked, would trigger a call to a server-side endpoint, like the example below:
$.ajax({
type: "POST",
crossDomain: true,
contentType: "application/json”,
url: "https://localhost:44343/api/Users/UserName/Notes",
dataType: "json",
data: "{'Title':'111', 'Content':'222', 'NoteIsActive':'true', 'IsVisible':'true'}",
success: function (response) {
[...]
}
error: function (response) {
console.log("Error 37!");
},
failure: function (response) {
console.log("Fail");
}
});
This code attempts to create a note for the user UserName by posting to the endpoint specified in the url field with the body described in the Json string from the data field. If CORS is not enabled, we would get an error of this type:
This error is visible in the browser console only. The request fails because of this and an error is returned.
To solve this in .NET Core 3.1, we need the following configurations done in the Startup.cs file from the web API. First, we declare a string to hold the name of the policy used for CORS:
readonly string allowSpecificOrigins = "_allowSpecificOrigins";
Then, in method public void ConfigureServices(IServiceCollection services), we need to add this code:
services.AddCors(options =>
{
options.AddPolicy(allowSpecificOrigins,
builder =>
{
builder.WithOrigins("https://localhost:44380")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Be aware that you need to specify the address of the user application in the WithOrigins() of the web API. This is in order for the web API to be able to accept requests from the user application.
Last but not least, in the public void Configure(IApplicationBuilder app, IWebHostEnvironment env) method of Startup.cs you need to add this code, which enables the configurations you just did:
app.UseCors(allowSpecificOrigins);
The location of this call is also important, meaning you should place it after app.UseRouting() but before app.UseHttpsRedirection() in case you are using Https.
You should no longer receive the error.
And the response from the web API returns successfully in our demo:
Other CORS options:
You have the option to use AllowAnyOrigin() instead of WithOrigins() if you want to accept requests from anywhere, but this causes security issues and in production should be avoided.
Also, if you want to be more precise and allow only the POST method, you can use WithMethods(“POST”) instead of AllowAnyMethod(). This also increases security of your API.
Conclusion
CORS can be seen as a relaxation attempt to the Same-Origin policy. ASP.NET Core allows us to take advantage of CORS in our cross-platform web applications. Hopefully, you have enjoyed this article and it provided the know-how in solving any CORS problems that you might have had.
Andriy Kravets is writer and experience .NET developer and like .NET for regular development. He likes to build cross-platform libraries/software with .NET.