CORS for Beginners

Matthew Holliday
The Software Integrator
6 min readSep 28, 2019

Modern web applications consist of a variety of resources served from different domains. This poses security challenges for browser developers, who have implemented policies to keep user data within safe boundaries. CORS allows web applications to take advantage of third-party-hosted services while still protecting user data.

The Problem

When you log into a web application, it commonly generates a cookie (a snippet of browser-stored information) containing a secret. This conveniently allows you to navigate around the website and initiate server-side actions without having to provide your login credentials over and over again. The browser sends along the authentication cookie with each request to verify to the domain’s server(s) that you have already logged in.

Some requests, of course, are not (directly) initiated by the user. Much of the power of modern web applications comes from the ability of JavaScript to independently communicate with the server; requesting data, updating data, and displaying it to the user.

Normally, this is fine. But things get dicier when those requests are issued by scripts in different domains.

Imagine that you have logged into your bank account. After you’ve logged in, the banking application generates a cookie. As you proceed to check your accounts (navigating to different pages, clicking buttons, etc.) the browser issues requests to the banking application’s server.

Now suppose that having seen some extra funds in your account, you decide to book a small vacation for yourself. Unfortunately, your investigations into affordable accommodations lead you to the website of the Shady Motel, which hosts a malicious script. When you load the Shady Motel’s homepage, the malicious script executes and attempts to issue a request to your banking application to steal your sensitive information.

How does the browser keep your sensitive banking information from being compromised in this situation?

The Great Wall of Separation

In order to ensure that malicious scripts do not send unauthorized requests to other websites on the user’s behalf, browsers enforce a set of rules called the same-origin policy. A browser that implements the same-origin policy (which includes all of the most popular modern browsers) forbids scripts from accessing data from another origin.

So what is an “origin” anyway?

Generally speaking, an origin is the combination of a URI scheme, hostname, and port number. This is a “good enough” approximation for most situations, but it is possible to encounter edge cases when browsers will behave differently or ambiguously.

RFC 6454, Section 4 provides guidelines on exactly how to determine if two origins are the same but the real-world determination of what constitutes different origins is ultimately up to the implementation of the browser.

When in doubt, consult the documentation of the browser(s) in question.

A Way Through the Wall

Of course, there are all sorts of legitimate reasons for a script to make a request to a server on a different domain. Most clients aren’t as shady as the Shady Motel.

As a developer, you’ll want your scripts to be able to call third-party APIs. And as a provider of a third-party API, you’ll want legitimate client applications to be able to reach your web service.

This is where CORS comes in.

CORS stands for Cross-origin Resource Sharing. It provides a standard way for the browser to relax the same-origin policy for servers that want to make themselves accessible to scripts on other domains.

Simple Requests vs Preflighted Requests

CORS distinguishes two types of requests, commonly referred to as “simple” and “preflighted.”

GET requests, HEAD requests, and some POST requests (those with MIME types of text/plain, application/x-www-form-urlencoded or multipart/form-data) are referred to as “simple requests.”

Other HTTP requests are “preflighted” to avoid unintended updates to the server. This means that the browser will send an HTTP OPTIONS request to the server which returns details to find out if the server will allow the calling script to make the HTTP request it wants. If the headers returned from the OPTIONS request indicates that the client’s intended call is allowed, a subsequent HTTP request is executed.

CORS HTTP Headers

CORS utilizes some portion of a standard set of HTTP headers, depending on whether a request is simple or preflighted.

The Request Headers

Client applications can utilize CORS by sending a number of required request headers. If you’re making an AJAX request, you don’t need to set these headers programmatically (the browser will add them for you.)

Origin: <uri>

The Origin header simply indicates which server the request is initiated from (just the server name, not the path.)

Access-Control-Request-Headers: <method>

Preflight requests use the Access-Control-Request-Method header to tell the server which method the subsequent (actual) HTTP request will use.

Access-Control-Request-Headers: <field-name(s)>

Preflight requests use the Access-Control-Request-Method header to tell the server which headers the subsequent (actual) HTTP request will use.

The Response Headers

The server uses CORS response headers to tell clients how they can safely access the server’s resources.

Access-Control-Allow-Origin: <origin> | *

The Access-Control-Allow-Origin header can be used by the server to specify that scripts at a certain origin may access the resource or that any origin may access the resource (by using the “*” wildcard.)

Access-Control-Expose-Headers: <header-name>[, <header-name>]*

The Access-Control-Expose-Headers header can be used by the server to specify which response headers the browser should have access to.

Access-Control-Max-Age: <seconds>

Browsers can improve the performance of CORS by caching the results of preflight requests. However, this poses a security challenge since the cached policy may not reflect the latest policy changes on the server. The Access-Control-Max-Age header tells the browser how many seconds the preflight response may be safely cached before the browser should check for possible updates on the server.

Access-Control-Allow-Methods: <method>[, <method>]*

By default, the browser will not send cookies or other credentials with the request. The Access-Control-Allow-Credentials header authorizes credentials to be sent.

(NOTE: If the Access-Control-Allow-Credentials flag is set to true, you can’t use the wildcard value with the Access-Control-Allow-Origin header.)

Access-Control-Allow-Methods: <method>[, <method>]*

What Integration Developers Need to Consider

  • Plan ahead. Make sure that third-party web services that you need to access from client-side scripts have a CORS whitelisting policy.
  • Understand who your client applications are. If client-side code needs to access your APIs, make sure your server-side application supports CORS and can distinguish known and unknown clients.
  • Check resources like MDN and the OWASP Foundation to make sure that your application is secure and standards-compliant.

--

--

Matthew Holliday
The Software Integrator

Programmer writing about machine learning, math, and software engineering. CEO at https://www.goldmountaintech.com/.