.Net Core 3.1 CORS

Cross Origin Resource Sharing centers on the fact that modern browsers won’t allow javascript programs running in them from making XHR requests to a foreign server unless the server expressly allows it. The browser considers foreign servers to be any server except the one from which the page originally came. The difference can be in any part of the “origin,” which is the left side of the URL concluding with the port.

  • https vs. http
  • domain or subdomain — sub.mydomain.com is different from www.mydomain.com
  • port — mydomain.com:1000 is different from mydomain.com:1001

Consider an Angular application served from https://mydomain.com that wants to converse with an authentication server hosted at https://auth.mydomain.com. Because the origins are different, the browser asks the server if it can access https://auth.mydomain.com.

CORS exists because of the modern need for a web page served from origin A to access origin B. In the past, you were just out of luck. Browsers blocked such connections. CORS was created to allow the access, and so is actually less secure for the server. (If the server needs to be secured, authentication should be used.) The primary issue is that when a malicious website running javascript on your computer — why did you click on that link!?! — sends background XHR requests to known URLs (say that well-known bank URL) the cookies on your machine, which may include session identifiers, are automatically sent along for the ride, so the malicious script now has access to your bank account website. Or, think of the trouble an errant javascript program could cause were it given access to your home router configuration “website.”

It should be noted that from the point of view of the Angular app this permissions request is done behind the scenes by the browser software. All the Angular app does is make the XHR call to the foreign API, as if it were perfectly accessible. If CORS fails — the Angular app’s origin is rejected or if there’s no CORS processing at all going on at the server — the javascript program is exited and an error is displayed in the console by the browser and the request appears to fail. (Actually, the auth request never was made because the CORS error short-circuited it.)

The background request is an http OPTIONS request that (again!) the browser sends automatically when it sees that the origin of the URL being requested is different from the one from which the javascript was served. See the example below. The requested URL (authority) is https://localhost:5001, but the origin of the request is http://localhost:4200. Note that “access-control-request-headers” tells the server which headers will be sent with the request, which in our case is Content-Type. We’re posting data (eventually) and so must tell the server what format the data is in. “Access-control-request-method” tells the server which method is being requested, POST in our case. “Scheme” tells the server we’ll be using https.

You can see that the browser is announcing it is

  • from origin: http://localhost:4200
  • requesting localhost:5001 on schema https
  • requesting permission to do a POST

The server responds with a message that either approves the request or rejects the request. If the request is rejected, the browser throws an error. Here, is the header of an approved OPTIONS request:

If the request is not approved, an http error is thrown. Otherwise, the client code (finally!) makes the POST request. (See below for the Angular code used to make our “authenticate” request.)

An important thing to realize is that the browser is responsible for requesting (and denying) access. If Postman or some other non-browser tool is used to make the request, no OPTIONS “pre-flight” request will be inserted and so the request will work just fine. See this page.

Of course, it’s possible to use Postman to send an OPTIONS request just like the browser does. Note the three non-highlighted request headers and the last header in the response below, which tells the caller that http://localhost:4200 is an allowed origin.

.Net Core 3.1 Server CORS Configuration

You don’t have to do anything to get CORS working in the browser. It does it by default. You can turn it off in your browser, though. Use this Chrome plugin or start up Chrome like this:

[PATH_TO_CHROME]\chrome.exe” –disable-web-security –disable-gpu –user-data-dir=~/chromeTemp

On the server, .Net Core has tried to make it easy to set up CORS. In the simplest case you just list the origins you want to give access the server. As with most .Net Core configuration, you do this in startup.cs.

In the Configure Services code below, the CORS policy is named “authenticate” (this could be anything) and is set up to allow requests from http://localhost:4200.

services.AddCors(options =>
   options.AddPolicy(name: "authenticate",
                     builder => {

There are other ways to set up CORS, such as using attributes, but the above works for most circumstances. (It even works for SignalR!)

After the CORS policy is set up, you pass the name (“authenticate” in the example) to the UseCors method in the middleware pipeline. In the Configure method of startup.cs, invoke CORS after routing:

// CORS must go here!          

The POST Request

In this example, we are authenticating the user from an Angular SPA by hitting an auth server. Note that the POST request must include a Content-Type header (application/json). Without it, the server throws an Unsupported Media Type (415) error. In Angular, set up the http headers like this

let httpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' });

Then the actual login request is made like this:

// post returns an Observable.
// The observable's "subscribe" method has 3 callbacks: next?, error?, and complete?
// jsonToken (a json string) is what we get back. It's used to authenticate SignalR 
// calls.
// Note: http header "Content-Type" tells the server the format of the data being sent,
// which in this case is 'application/json' or a JSON object (model, below) converted
// into a string with "stringify."
// Without this header, the server returns a 415 error.

this.http.post<jwsToken>(url, JSON.stringify(model), 
                        { headers: httpHeaders}).subscribe(
			(x: jwsToken) => {
			// next
			if (x.token != undefined && model.username != undefined) {
			    var token: string = x.token;
			    this.signalrService.setJWTtoken(token, model.username);
			(error) => {
				// error
			() => {
				// complete

You may also like...