CORS is an important security mechanism to prevent malicious cross-origin requests. It’s also confusing for developers, with cryptic error messages and seemingly endless configuration options.

In this article, we’ll uncover precisely why CORS exists, how it works, and how best to setup the browser and server for secure cross-origin communication.

1. What problem does CORS solve?

To understand CORS you need to understand the concept of an origin.

Origin: a specific protocol, host, and port combination

An example of an origin is That’s the same as shown in the browser address bar, which assumes port 443 by default.

An example of a different origin is Even though this origin contains the same base domain as, it has an apis subdomain, which makes it unique.

Get the idea? 🤔

To put it another way, the origin is the part of the URL before the path. It determines which server a request is sent to, since the domain name resolves to an IP address, via a DNS lookup. Every HTTP request made from a webpage in a browser has an origin.

Origins and HTTP requests

When you access the webpage at in your browser, this is the origin:

Request URL Origin

This initial request returns an HTML document, which itself makes further requests. But this time, to another origin:

Request URL Origin

So the main document makes requests to other origins. We call these cross-origin requests.

How do you know if a request is cross-origin?

Check the origin part of the website URL shown in the browser address bar. If it’s different from the origin part of the request URL, you’re cross-origin, baby!

Determining cross-origin requests in a browser

In fact, most websites make such cross-origin requests.


Three main reasons:

  1. it’s convenient to store assets like JavaScript, images, and CSS on a different server

  2. it’s clearer to host different parts of a website on different subdomains e.g.

  3. JavaScript code may need to make API calls to other 3rd party services

OK, so websites need to make requests to different origins. So what?

Browser default behaviour

Well, depending on the webpage resource from which the cross-origin request originates, the browser may or may not allow it.

Browsers by default allow cross-origin requests from:

But, browsers by default deny cross-origin requests from:

  • JavaScript code using the fetch API or XMLHttpRequest

  • some less common resources (see full list)

This policy is known as the same origin policy. Browsers implement it to prevent certain attacks, where malicious websites run JavaScript code requesting data from other websites.

For example, without the same origin policy, a dodgy website could make a request to, get all your account information, then send it back to their dodgy server.

The same origin policy prevents cross-origin JavaScript requests

But making cross-origin requests through JavaScript is a very common requirement for modern websites e.g. calls This is the main use case we’ll explore in this article.

But before we get into how CORS enables cross-origin requests, how did webpages work before CORS?

2. What happened before CORS?

Before CORS, cross-origin requests from JavaScript code had to be made to the same origin.

If you think that was a big problem, you’re right! Web developers were restricted in the experiences they could create, but still had a few options available:

  • form data could be posted cross-origin

  • images, CSS, and scripts could still be loaded from another origin

  • servers were free to make requests to any origin they liked

Although such workarounds existed, many developers felt that the same origin policy was too restrictive. Even though it helped prevent malicious cross-origin attacks, surely there was an alternative?

Fortunately, in 2009, a way to relax this same-origin restriction was introduced, called CORS.

3. How does CORS work?

Cross-Origin Resource Sharing (CORS) is a relaxing of the same origin policy rules. With CORS, and under certain conditions, browsers allow JavaScript code running on a site from one origin to send requests to another origin.

With CORS, it’s up to the server to decide which cross-origin requests are allowed.

That’s great, because now an API hosted at can allow cross-origin requests from, but block requests from 👍

CORS overview

With CORS, the API chooses which cross-origin requests are allowed

So how does CORS actually work?

Well, there are 2 main parts to it:

  1. the browser: detects when JavaScript code tries to make a cross-origin request, and intercepts that request either before it’s sent, or before the response is returned

  2. the server: tells the browser which origins are allowed to make requests, by sending back special CORS HTTP headers

HTTP headers are key-value pairs that get sent in the HTTP request, along with the method and body.

Fortunately, the browser component of CORS is mostly taken care of automatically. For example, Chrome intercepts requests made from JavaScript, and validates with the server that they’re acceptable.

Most of the CORS configuration that developers need to make is on the server. That makes sense, since the server is in charge of deciding which cross-domain requests to allow.

But before learning how to configure the server for CORS, we need to explore in detail what happens when the browser intercepts a cross-origin request. Depending on the type of request, the browser will deal with it in one of two distinct ways.

  1. simple requests: send the original request and validate CORS headers on the response

  2. pre-flighted requests: send a pre-flight request, validate it, then send the original request

Let’s explore these two approaches in more detail.

Approach 1: Browser validates origin on response

With this approach, the browser sends the cross-origin request without any up-front checks.

When the response is received, the browser looks for a specific header, Access-Control-Allow-Origin. This header, sent by the server, tells the browser which origins are allowed to handle the request.

Access-Control-Allow-Origin <origin>

The value of the header can be a single origin, or * to represent any origin.

On inspecting this header, the browser has to deal with 3 possibilities:

  1. the response has an Access-Control-Allow-Origin header, and it contains the origin from which the cross-origin request was made. The browser accepts the response and the JavaScript code gets access to it.

  2. the response has an Access-Control-Allow-Origin header, but it doesn’t contain the origin from which the cross-origin request was made. The browser rejects the response, and the JavaScript code gets an error.

  3. the response doesn’t have an Access-Control-Allow-Origin header. The browser rejects the response, and the JavaScript code gets an error.

So the browser works out whether the request comes from a valid origin, thanks to the header sent from the server. This approach is used when the request is a simple request.

Simple requests are:

  • GET with only default headers (discussed later)

  • HEAD

  • POST where Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain

An example

Consider what happens when a GET request is made from a webpage at the origin to an API at the origin

Request headers

The browser sets many other headers automatically, but Origin is the most relevant right now.

Once the server receives the request, it sends this response.

Response status code
200 OK
Response headers
Response body
{"message":"Success response from GET request"}

In this case, the server responded with the Access-Control-Allow-Origin header, with a value equal to the request origin. The browser accepts the response, passing it to the JavaScript code, which continues execution.

Try this example in the Interactive CORS Simulator.

Does JavaScript know about CORS? From JavaScript’s perspective, CORS doesn’t exist. It’s all handled by the browser. So when you call fetch(<request-url>), either a response is returned or an error is thrown, depending on the result of the CORS check.

Approach 2: Browser validates origin in pre-flight request

The other approach the browser uses to allow cross-origin requests applies to all other requests types, including:

  • POST (where Content-Type is not application/x-www-form-urlencodedmultipart/form-data, or text/plain)
  • PUT

Since these types of request might manipulate data on the server-side, they must be validated by the browser before the request is sent to the server. This is achieved with another request, called a pre-flight request, sent automatically to the server by the browser.

The pre-flight request is a special OPTIONS type request, which the server must be able to handle.

The browser automatically adds these headers to the pre-flight request:

  • Origin: as described above
  • Access-Control-Request-Method: specifies the HTTP method the browser will use in the main request
  • Access-Control-Request-Headers: specifies the headers the browser will send in the main request

These headers are a way for the browser to tell the server what to expect in the main request. If the details match what the server allows, the server should respond with full details of what requests are acceptable.

The server adds these headers on the response to the pre-flight request:

  • Access-Control-Allow-Origin: specifies the origin from which the main request may be made
  • Access-Control-Allow-Methods: specifies allowed methods for the main request
  • Access-Control-Allow-Headers: specifies allowed headers for the main request

The browser checks the pre-flight response to decide if the main request can be made. If it can, it makes it and passes the response to the JavaScript code. Otherwise, the browser doesn’t make the main request, and instead returns an error to the JavaScript code.

Note that the pre-flight response headers can include values that weren’t in the request headers. Why? Because the pre-flight response includes all available headers and methods for the specific path. As you’ll see later, this enables the pre-flight response to be cached to avoid future pre-flight requests.

Error response to pre-flight request: During the pre-flight request, the server may determine that the provided CORS headers are invalid. It can then return a 403 Forbidden response, which will cause the browser to reject the main request and return an error to the JavaScript code.

e.g. if the browser sends Access-Control-Request-Method: DELETE in the pre-flight request, but the server only accepts POST requests, it can return a 403 error.

An example

Consider what happens when a POST request is made from a webpage at the origin to an API at the origin

Since this is a POST request with a Content-Type of application/json, the browser intercepts the request to send a pre-flight OPTIONS request.

Request headers
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

The browser includes CORS headers in the request, with values describing the cross-origin request that the JavaScript code wants to make.

The server returns this response to the pre-flight request:

Response headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type

This tells the browser that the main request can be made, since its origin, method, and headers are compatible.

So the browser makes the main request:

Request headers
Content-type: application/json

The server processes the request, and sends this response:

Response status code
200 OK
Response headers
Response body
{"message":"Success response from POST request"}

In this case, the browser accepts the response and returns it to the JavaScript code.

Did you notice how the server includes the Access-Control-Allow-Origin header in the response to the main request too? If this header isn’t set correctly, the browser can still reject the response and return an error to the JavaScript code.

Try this example in the Interactive CORS Simulator.

4. Do CORS requests include headers?

CORS has strict rules about what request headers can be sent to and returned from another origin.

Request headers

Without any additional configuration, only the following headers are allowed:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type with a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain

So what to do if you want to send other headers?

Again, the API defines what headers can be included. The server tells the browser this through the Access-Control-Allow-Headers header, included in the response to the pre-flight request.

The value must be a comma-separated list of allowed header names.

Access-Control-Allow-Headers: <header name 1>,<header name 2>,...

For example, suppose we run this JavaScript code to make a cross-origin GET request, including a custom header:

fetch("", {
    headers: {
        Cool-Stuff: "pi=3.14"

Since this GET request has a custom header, it’s no longer a simple request and the browser sends an additional pre-flight request.

Request headers
Access-Control-Request-Headers: cool-stuff
Access-Control-Request-Method: GET

The browser automatically adds Access-Control-Request-Headers, with a value based on the fetch function call.

The server processes this, and returns this response:

Response headers
Access-Control-Allow-Headers: cool-stuff
Access-Control-Allow-Methods: GET

With this response, the server has told the browser that it’s permitted to send the main request with the custom header, which it does:

Request headers
cool-stuff: pi=3.14

Try this example in the Interactive CORS Simulator.

Simple vs. pre-flighted requests: you’ve seen that sending an additional header can trigger the browser to send a pre-flight request for a GET request. To ensure you’ve got appropriate OPTIONS APIs exposed, use a framework which creates these automatically, as discussed later.

Response headers

Just like CORS restricts what request headers can be sent in a cross-origin request, restrictions also exist on the response headers.

The difference is that the server can send any response headers it likes, but only the configured headers are exposed to the JavaScript code. In other words, the browser filters out disallowed response headers and constructs the JavaScript HTTP response as though they were never returned.

How does it work?

You guessed it, another CORS-specific response header! This time it’s Access-Control-Expose-Headers, which takes a comma-separated list of response headers to be returned to the JavaScript code.

Access-Control-Expose-Headers: <header name 1>,<header name 2>,...

For example, suppose we have this JavaScript code, which makes a simple GET request:


The request is sent cross-origin with the Origin header:

Request headers

The server needs to expose a header to the calling JavaScript, so it sends this response back to the browser.

Response status code
200 OK
Response headers
Access-Control-Expose-Headers: Best-Header-Ever
Best-Header-Ever: If you're reading this, it worked!
Response body
{"message":"Success response from GET request"}

The Access-Control-Expose-Headers header tells the browser that the specified header can be returned to the JavaScript code. The JavaScript code can then use the header value as required.

For example, the above code could be modified to log the header value:

    .then(response => console.log(response.headers.get("Best-Header-Ever")));

Try this example in the Interactive CORS Simulator. There’s also an example for a pre-flighted request, which works in the same way.

Included response headers: to return any of these response headers to JavaScript, you don’t need to set the Access-Control-Expose-Headers header: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma.

You’ve now seen several success-case CORS examples, for both simple and pre-flighted requests. But things don’t always go to plan, so let’s see what might go wrong with cross-origin requests.

5. Why do I get CORS errors in my browser?

Here are some common CORS-related errors you might see in the browser.

Error from GET request with missing header

When making a cross-origin GET request you could get this error in the Chrome JavaScript console.

Access to fetch at ‘' from origin ‘’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

In this case, the response was missing the Access-Control-Allow-Origin header, so it was rejected by the browser. The browser must validate that the request origin matches the response header to pass the response back to the JavaScript code.

Resolution: add an Access-Control-Allow-Origin header containing the origin

Try this example in the Interactive CORS Simulator.

Error from GET request with misconfigured header

When making a similarGET request, you might instead see this error.

Access to fetch at ‘' from origin ‘’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header has a value ‘’ that is not equal to the supplied origin.

This time, the server has returned the Access-Control-Allow-Origin header, but it was misconfigured. Since the browser couldn’t match the request origin with the value in the header, the response was rejected.

Resolution: configure the Access-Control-Allow-Origin header to have the origin

Try this example in the Interactive CORS Simulator.

Error from POST request with missing OPTIONS API

When making a POST request with a Content-Type of application/json, the browser sends a pre-flight OPTIONS request. If this API doesn’t exist, the server returns a 404 Not Found response, and the browser shows this message in the JavaScript Console.

Access to fetch at ‘' from origin ‘’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

Notice how the error message doesn’t mention anything about the 404 Not Found response? In Chrome, to see the response details look in the Network tab (see next section for more details).

Resolution: add an OPTIONS API, returning headers Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Allow-Origin .

Try this example in the Interactive CORS Simulator.

So you’ve seen a few error examples, but does this cover all the possibliites?

Actually, no, because there are just too many. But there are other ways to figure out what’s going on when you get a CORS issue.

6. How to debug CORS issues in the browser?

Let’s run through some techniques to diagnose CORS issues yourself, within the browser.

Diagnosing simple request problems

When your JavaScript code makes a simple request, which doesn’t require a pre-flight request, you can see helpful information in the JavaScript Console.

CORS error shown in Chrome’s JavaScript Console

In this case, the error is clearly described, telling us that the Access-Control-Allow-Origin header has been incorrectly configured.

Diagnosing pre-flight request problems

For pre-flighted requests, we need to see details of the 2 separate network requests:

  • the pre-flight request

  • the main request

To view this information in Chrome of Firefox, open the Network tab in Developer Tools, accessed with F12.

CORS pre-flight and main request in Firefox’s Web Developer Tools

In this specific case, the pre-flight request has an error. Click the request to see more details

A 404 Not Found response was received from the pre-flight request. The server should be updated to implement an OPTIONS API.

7. How to implement CORS on the server?

If you’ve followed the troubleshooting steps above, and you know what the problem is, what next?

To resolve almost all CORS issues, you’ll have to make changes to your API to:

  • include missing CORS headers

  • fix misconfigured CORS headers

  • add a missing OPTIONS API

What this involves depends on the technology your API uses. For example, you might need to add code to set a header name and value, or configure a framework to do it for you.

To help you out, here’s how you can configure CORS using 3 popular API technologies.

Implement CORS in Spring Boot

The Java Spring Boot framework can automatically add the required CORS headers to the response and expose an OPTIONS API to service pre-flight requests. You just need to configure which endpoints should be cross-origin enabled.

You can do that at the API level, on an individual controller method, with the @CrossOrigin annotation:

    @GetMapping(value = "/ride", produces = MediaType.APPLICATION_JSON_VALUE)
    public Iterable<ThemeParkRide> getRides() {
        return themeParkRideRepository.findAll();

Or at the global level, by adding a WebMvcConfigurer configuration bean to a @SpringBootApplication or @Configuration class.

    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            public void addCorsMappings(CorsRegistry registry) {

Each of these approaches also lets you cofigure the allowed origins, methods, and headers.

Here’s how the API-level annotation looks to set an allowed origin:

@CrossOrigin(origins = "")

Implement CORS in JavaScript

If you’re writing an API in JavaScript, simply return the required CORS headers by setting them in code.

Here’s an example in a Node.js handler for a simple GET request, designed to run in AWS Lambda.

module.exports.handler = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
        message: 'Success response from GET request'
    headers: {
      "Access-Control-Allow-Origin": "*",

For requests requiring a pre-flight request, you can simply check the request method, then return a different response if it’s OPTIONS.

module.exports.handler = async (event) => {
  const allowedOrigins = "*";
  const allowedHeaders = "*";

  if ('OPTIONS' === event.requestContext.http.method) {
    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": allowedOrigins,
        "Access-Control-Allow-Headers": allowedHeaders,

  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": allowedOrigins,
    body: JSON.stringify(
        message: 'Success response from POST request'

This approach is more manual than other frameworks, such as Spring Boot, but it gives you ultimate control. The responses to the APIs called by the Interactive CORS Simulator use this approach.

Implement CORS in AWS

One way to move CORS logic out of the code and into the infrastrcture is to use AWS API Gateway. It’s like a load balancer, routing requests to AWS Lambda functions, which run code. Importantly, it has a Configure CORS page.

Here you set values for the different CORS headers, which API Gateway then adds automatically to any Lambda function response. It also adds an appropriate pre-flight OPTIONS API.

Configuring CORS in AWS API Gateway

The benefit of using this approach is that once it’s setup, additional API Gateway routes will automatically inherit this CORS configuration.

8. Do CORS requests include cookies?

One security vulnerability with cross-origin requests is where a malicous website sends an authenticated JavaScript request to an API at another origin. The request could include cookies, providing access to sensitive data from the API e.g. banking data. This could then be returned to the owner of the malicious website.

Fortunately, CORS offers a solution to this. By default, cookies cannot be sent in a cross-origin JavaScript request. To try to do so, without following the steps below, produces this JavaScript error:

Access-Control-Allow-Credentials’ header in the response is ’’ which must be ’true’ when the request’s credentials mode is ‘include’.

To include cookies, the server must return an Access-Control-Allow-Credentials header with a value of true.

For example, suppose you’re accessing a website The following JavaScript code, running on the page, attempts a request to the origin

fetch("", {
    credentials: 'include'

The credentials: include property tells the browser to include any cookies available for the domain.

In this case, we’re making a simple GET request, without a pre-flight request. So the request might look like this:

Request headers
Cookie: Authorization=123abc;<other cookie 1>;<other cookie 2>

Here, the request includes the Cookie header, with a cookie set by a previous request.

Now the server validates the cookie and returns a success response:

Response status code
200 OK
Response headers
Access-Control-Allow-Credentials: true
Response body
{"balance":"10000000", "currency": "GBP"}

There are 2 important requirements for the response:

  1. the Access-Control-Allow-Credentials header must be set to true. If it’s missing, you’ll receive the JavaScript error shown above.

  2. the Access-Control-Allow-Origin header cannot be the wildcard * any origin value. It must be set to a specific origin.

If the response meets these requirements, the browser accepts it and retrurns it to the JavaScript code.

Try this example in the interactive CORS simulator. Or see how credentials are sent with a pre-flighted request.

Setting cookies: to receive cookies via the Set-Cookie response header, follow the same steps described above i.e. set the Access-Control-Allow-Credentials and Access-Control-Allow-Origin response headers appropriately.

9. Do pre-flights requests cause slowdown?

Since some cross-origin requests require an additional pre-flight request, it’s sensible to ask whether this could have a performance impact on a website.

The good news is that by default the browser caches the response to the pre-flight request for 5 seconds. This means that any additional pre-flighted requests made by JavaScript, within the time period, can use the cached response.

If you want to change the cache time, then return an Access-Control-Max-Age header in the pre-flight response.

Here are some example values the header can take:

  • 0 means no caching

  • 5 is the default value

  • 600 would be 10 minutes

To see a caching example in practice, check out the Interactive CORS Simulator.

10. What are some CORS best practices?

Even though the ideas behind CORS are simple, there are lots of implementation details and edge cases to consider.

Here are 3 best practices to keep in mind when implementing CORS:

  1. Avoid a value of * for Access-Control-Allow-Origin CORS empowers the server to decide which origins browsers can send requests from. Be specific when configuring the Access-Control-Allow-Origin header. For example, if your website is hosted at, then including that origin may be sufficient.

  2. Rely on server frameworks as much as possible Since there are so many different headers and edge cases to remember, if you try to implement CORS yourself, you’ll probably make a mistake. Instead, rely on well-used 3rd-party server frameworks to do the heavy lifting e.g. Spring Boot or AWS API Gateway, as described above.

  3. Test your CORS logic in the browser Once you’ve setup CORS on the server, you’ll inevitably test that your website works as expected in the browser. It’s also worth testing the inverse, that a server request from an unexpected origin is blocked by the browser’s CORS policy. This can be as simple as running the fetch function from the JavaScript Console when viewing a website from an unrelated origin.

11. CORS header summary table

We’ve covered all 8 CORS request and response headers, which can all be conveniently identified by their Access-Control prefix.

Request headers

Any required request headers are set automatically by the browser when you make a cross-domain JavaScript fetch request.

Header name Main or pre-flight request? Purpose Value
Access-Control-Request-Headers Pre-flight Defines HTTP headers to be sent in the main request Comma-separated list of header names
Access-Control-Request-Method Pre-flight Defines HTTP method to be used for the main request Single request method

Response headers

Header name Main or pre-flight request? Purpose Value
Access-Control-Allow-Origin Both Defines origin from which browser can make requests * or explicit origin
Access-Control-Allow-Methods Pre-flight Defines possible request methods to use for main request Comma-separated list of request methods
Access-Control-Allow-Headers Pre-flight Defines possible headers to send in main request Comma-separated list of header names
Access-Control-Expose-Headers Main Defines headers for browser to expose to JavaScript Comma-separated list of header names
Access-Control-Allow-Credentials Both Defines whether credentials can be included in the request true (false when header omitted)
Access-Control-Max-Age Pre-flight Defines for how long the browser should cache the pre-flight response Number of seconds (default 5)

12. Final thoughts

You should now have a good understanding of why CORS exists, how it works, and how to implement it in many different use cases.

To fully understand these ideas, I recommend trying out the examples described above yourself. The easiest way to do this is to run through the Interactive CORS Simulator, which showcases many concepts from this article.