Web Security

CORS (Cross-Origin Resource Sharing)

About 4 min read

What Is CORS (Cross-Origin Resource Sharing)

CORS (Cross-Origin Resource Sharing) is an HTTP header-based mechanism that safely relaxes the browser's same-origin policy so that resources can be shared across different origins. An origin is defined by three elements: the scheme (http/https), the host name, and the port number.

The same-origin policy is a browser security mechanism that stops a script loaded from one origin from reading a response from another origin. Without this restriction, a malicious site could call the API of a bank site you are logged in to through your browser and read the balance and transaction history contained in the response with its own script.

The relationship worth keeping straight is this: the side that protects you by default is the same-origin policy, and CORS is the mechanism for relaxing that restriction selectively. Reading it as "configure CORS to block cross-origin attacks" has the direction backwards - in practice it is the act of opening, only as far as necessary, something that is closed by default. One more point: what the same-origin policy stops is the reading of the response, not the sending of the request. The fact that the send itself still succeeds is why CSRF remains a real threat.

In modern web applications, however, it is common for the frontend and the backend API to run on different domains. CORS makes the cross-origin communication you need possible in a safe way, by having the server state explicitly in HTTP response headers which origins it allows access from.

How CORS Works

CORS works in one of two ways depending on the type of request.

Simple Request

When the method is GET, HEAD, or POST, the headers attached stay inside the safelist - Accept, Accept-Language, Content-Language, and Content-Type - and the value of Content-Type is one of application/x-www-form-urlencoded, multipart/form-data, or text/plain, the request is treated as a "simple request". Put the other way round, adding a single header of your own such as Authorization or X-Requested-With takes it out of that category.

For a simple request, the browser sends the request as it is and checks the Access-Control-Allow-Origin header of the response. If the origin is allowed, the response is handed to JavaScript; if not, the read is blocked. What is blocked is only the read from JavaScript - the request did reach the server and was processed. Data can be created or updated on the server even while a CORS error is showing in the browser console, and this difference is something to keep in mind whenever you narrow down a problem during development.

Preflight Request

When a request does not meet the conditions for a simple request - PUT, DELETE, or a request carrying custom headers, for example - the browser sends a "preflight request" with the OPTIONS method ahead of the actual request. The server answers with the following headers to state what it permits.

  • Access-Control-Allow-Origin: the origin that is allowed
  • Access-Control-Allow-Methods: the HTTP methods that are allowed
  • Access-Control-Allow-Headers: the request headers that are allowed
  • Access-Control-Max-Age: how long the preflight result is cached (in seconds)

If the preflight response satisfies those conditions, the browser sends the actual request.

Requests with Credentials

A cross-origin request that includes cookies or an authentication header requires Access-Control-Allow-Credentials: true. In that case the wildcard (*) cannot be used for Access-Control-Allow-Origin, and a specific origin has to be given. The wildcard counts as a wildcard only for requests without credentials - an exclusion written into the specification, so there is no way to satisfy both at once. The same goes for * in Access-Control-Allow-Methods and Access-Control-Allow-Headers: with credentials attached they are not treated as wildcards.

Another pitfall is that the preflight itself carries no cookies (the specification fixes the credentials mode of a preflight to same-origin). If a server or reverse proxy configuration demands authentication for the OPTIONS request as well, it fails before the actual request is ever reached, and the result is a CORS error whose cause is hard to see.

Common Misconfigurations and Security Risks

A misconfigured CORS policy cancels the protection the same-origin policy provides and creates serious security risks.

  • Casual use of Access-Control-Allow-Origin: *: allowing every origin means any site can reach the API. It should not be used outside public APIs, and never for an API that handles credentials
  • Reflecting the Origin header without validating it: an implementation that copies the value of the request's Origin header straight into Access-Control-Allow-Origin is effectively the same as a wildcard. It should be validated against a whitelist
  • Allowing the null origin: allowing Access-Control-Allow-Origin: null opens up access from sandboxed iframes and local files, which attackers can abuse
  • Over-permissive subdomains and suffix matching: when you allow a pattern such as *.example.com, an attacker who takes over a subdomain can abuse CORS. Worse, an implementation that only checks whether the end of the string matches will also let through a separate domain the attacker registered, such as evil-example.com

A misconfigured CORS policy can widen the damage when it is combined with XSS or CSRF. Tightening CORS, on the other hand, does not prevent CSRF: a simple request such as a form submission reaches the server without triggering a preflight, so CSRF needs its own countermeasures, namely tokens and the SameSite attribute on cookies.

Best Practices for Safe CORS Configuration

The following guidelines help you configure CORS safely.

  • Manage the allowed origins with a whitelist: keep the list of allowed origins in environment variables or a configuration file, and validate the request's Origin header against it by exact match
  • Allow only the methods and headers you need: list only what is actually used in Access-Control-Allow-Methods and Access-Control-Allow-Headers
  • Make use of the preflight cache: set Access-Control-Max-Age explicitly to reduce how often preflight requests happen. When it is left unspecified the default in the specification is only 5 seconds, so an OPTIONS request goes out ahead of practically every request. Browsers do cap the cache duration, though, and a value above that cap is trimmed down to it, so writing an extremely long number does not extend the effect
  • Handle requests with credentials carefully: when you set Access-Control-Allow-Credentials: true, restrict the allowed origins strictly
  • Use it together with CSP and other security headers: build defense in depth by combining CORS with other security headers instead of relying on CORS alone
  • Assume HTTPS: keep HTTP origins out of the allow list, so that an origin cannot be spoofed through a man-in-the-middle attack

CORS is a browser-side control and does not apply to server-to-server communication or to requests from command-line tools such as curl. It has no effect on the route where an attacker calls the API directly from their own environment, so do not rely solely on CORS - always implement server-side authentication and authorization.

CORS was standardized as the W3C Recommendation "Cross-Origin Resource Sharing" (January 16, 2014) and was later folded into the WHATWG Fetch Standard. When you need to check fine-grained behavior, the Fetch Standard is the accurate place to look.

To learn more about this topic, see HTTP Security Headers: 5 Essential Headers to Protect Your Website.

Common Misconceptions

CORS is a security feature that protects the server
CORS is a browser-side control and does not protect the server directly. Its restrictions do not apply to curl or to server-to-server communication. What forbids reading a response from another origin in the first place is the same-origin policy, and CORS is the mechanism that relaxes that restriction. It is a layer that protects the browser's user, and it sits at a different layer from server-side authentication and authorization.
If you get a CORS error, just use a wildcard (*) to fix it
A wildcard allows access from all origins, posing a high security risk. It cannot be used with requests that include credentials. The correct approach is to add only the necessary origins to a whitelist.
Share

Related Terms

Related Articles