What Is CORS?

Article thumbnail for What Is CORS?

You build a frontend app. You build a backend API.

Both work fine on their own.

But once the frontend sends a request to the backend, the browser shows an error like this:

"Access to fetch at 'http://localhost:3000/messages' from origin 'http://localhost:5173' has been blocked by CORS policy [...]"

That's a CORS error.

And if you're new to web development, it can be quite confusing.

Because the backend might be working. The URL might be correct. The request might even reach the server.

Still, the browser blocks your frontend code from using the response.

So let's explore what CORS is, why it exists and how you typically fix those errors.

What Is CORS?

CORS stands for Cross-Origin Resource Sharing.

It's a browser security mechanism that controls whether JavaScript code from one origin may read responses from another origin.

In simpler words:

If your frontend app is loaded from one URL and it tries to fetch data from another URL, the browser checks whether that is allowed.

If the server does not explicitly allow it, the browser blocks your frontend code from accessing the response.

That's CORS.

What Is An Origin?

An origin is defined by three things:

  • Protocol / scheme
  • Domain / host
  • Port

For example:

http://localhost:5173

This origin consists of:

  • Protocol: http
  • Host: localhost
  • Port: 5173

These are different origins:

http://localhost:5173
http://localhost:3000
https://localhost:5173
https://api.example.com
https://example.com

Even if two URLs look very similar, a different protocol, domain or port means: different origin.

That's why CORS errors are so common during development.

Your frontend might run on localhost:5173 and your backend API on localhost:3000. For the browser, those are two different origins.

Why Does CORS Exist?

CORS exists because of the same-origin policy.

The same-origin policy is a browser security concept. It prevents JavaScript code from one website from freely reading data from another website.

Imagine this would be allowed without restrictions:

  1. You're logged into your online banking website
  2. You open some malicious website in another tab
  3. That malicious website sends requests to your banking website from your browser
  4. It reads the responses and sends your private data away

That would obviously be a huge problem.

Browsers therefore restrict what cross-origin JavaScript code can read.

CORS is the controlled exception to that rule.

It allows a server to say:

"Yes, this other origin may read my responses."

Or:

"No, this origin is not allowed."

Where Does CORS Apply?

CORS is mainly relevant for browser-based JavaScript requests, for example requests sent with fetch() or XMLHttpRequest.

It can also matter for some other browser features like fonts, images used in canvas, WebGL textures and similar cases.

But the most common case is simple:

A frontend app in the browser wants to call a backend API on a different origin.

Important: CORS is enforced by browsers.

If you send the same request with Postman, curl, a backend server or a mobile app, CORS does not block it.

That's why CORS is not a general API security mechanism. It's a browser security mechanism.

How CORS Works

When a browser sends a cross-origin request, it includes an Origin header.

For example:

Origin: http://localhost:5173

The server can then respond with CORS headers.

The most important one is:

Access-Control-Allow-Origin: http://localhost:5173

That response header tells the browser:

"Frontend code loaded from http://localhost:5173 may read this response."

If the header is missing, or if it contains a different origin, the browser blocks the response from your JavaScript code.

The request may still have reached the server. The server may even have sent a response.

But the browser does not expose that response to your frontend code.

Simple Requests And Preflight Requests

Some cross-origin requests are sent directly.

Others first trigger a so-called preflight request.

A preflight request is an automatic OPTIONS request sent by the browser before the actual request.

It basically asks the server:

"May I send this real request with this method and these headers?"

You often see preflight requests when your frontend uses:

  • Methods like PUT, PATCH or DELETE
  • Custom headers
  • An Authorization header
  • JSON requests with Content-Type: application/json

A preflight request can look like this:

OPTIONS /messages HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type, Authorization

The server must answer with headers that allow the requested method and headers:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

If that preflight response is missing or incorrect, the browser never sends the actual request.

So if you see an OPTIONS request in your network tab: That's not a bug. That's the browser checking CORS permissions.

Important CORS Headers

There are a couple of CORS headers you should know.

You won't always need all of them, but these are the most important ones.

Access-Control-Allow-Origin

This header defines which origin may read the response.

Example:

Access-Control-Allow-Origin: https://my-frontend.com

You can also use *:

Access-Control-Allow-Origin: *

That means any origin may read the response.

This can be fine for public APIs where no credentials are involved. But don't blindly use it for everything.

And important: You can't use * together with credentials such as cookies.

Access-Control-Allow-Methods

This header tells the browser which HTTP methods are allowed for preflighted requests.

Example:

Access-Control-Allow-Methods: GET, POST, PATCH, DELETE

If your frontend sends a PATCH request but the server only allows GET, POST, the browser blocks it.

Access-Control-Allow-Headers

This header tells the browser which non-standard / non-simple request headers may be sent.

Example:

Access-Control-Allow-Headers: Content-Type, Authorization

You need this if your frontend sends JSON, auth tokens or custom headers.

Access-Control-Allow-Credentials

This header matters when cookies or other credentials are involved.

Example:

Access-Control-Allow-Credentials: true

On the frontend, you also have to opt into credentials:

fetch('https://api.example.com/user', {
  credentials: 'include',
})

If you use credentials, the server must return a specific allowed origin. It may not use Access-Control-Allow-Origin: *.

Access-Control-Max-Age

This header lets the browser cache a successful preflight response for some time.

Example:

Access-Control-Max-Age: 86400

That can reduce the number of preflight requests. Browsers may still apply their own limits, but it can help.

Access-Control-Expose-Headers

By default, frontend JavaScript can only read a small set of response headers.

If your API returns custom response headers that frontend code should read, expose them explicitly:

Access-Control-Expose-Headers: X-Total-Count

Vary: Origin

If your server dynamically allows different origins, you should also set:

Vary: Origin

This matters for caches.

It tells caches that the response can vary depending on the Origin request header. Without it, a shared cache might accidentally reuse a response with the wrong CORS header.

How To Fix CORS Errors

The most important rule is this:

CORS is fixed on the server, not in the frontend request.

This will not fix CORS:

fetch('http://localhost:3000/messages', {
  headers: {
    'Access-Control-Allow-Origin': '*',
  },
})

Those headers must be sent by the backend response.

A very simple backend response could include:

Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

And your backend must also handle OPTIONS requests if the browser sends preflight requests.

In development, frameworks or dev servers sometimes offer proxy features that avoid CORS by forwarding requests through the same origin. That's fine for development.

But in production, your real API should be configured with the correct CORS policy.

Common CORS Mistakes

There are a few mistakes that come up again and again.

Adding CORS Headers To The Frontend Request

As mentioned above: This does not work.

CORS headers like Access-Control-Allow-Origin are response headers. They must come from the server that owns the resource.

Thinking CORS Protects Your API From Everyone

CORS does not stop all requests.

It stops browser JavaScript from reading responses when the server did not allow it.

It does not stop Postman, curl, backend servers or attackers from sending requests directly.

So you still need real security:

  • Authentication
  • Authorization
  • Input validation
  • CSRF protection where needed
  • Rate limiting where needed
  • Proper secret handling

CORS is not a replacement for any of that.

Allowing Every Origin With Credentials

This is dangerous and often invalid.

If your API uses cookies or sessions, don't use Access-Control-Allow-Origin: *.

Use a strict allowlist of trusted origins instead.

Forgetting The Preflight Request

If your backend only handles GET and POST, but the browser first sends an OPTIONS request, your actual request may never happen.

Make sure your server answers preflight requests with the right CORS headers.

Returning CORS Headers Only For Successful Responses

CORS headers should usually also be included on error responses.

Otherwise, the browser may show a CORS error even though the real problem is a 401, 403 or 500 response.

That can make debugging much harder.

A Practical Checklist

If you run into a CORS error, check this:

  1. What is the frontend origin? Protocol, host and port.
  2. What is the backend origin?
  3. Does the backend response include Access-Control-Allow-Origin?
  4. If a preflight request is sent, does the backend handle OPTIONS?
  5. Are all required methods listed in Access-Control-Allow-Methods?
  6. Are all required headers listed in Access-Control-Allow-Headers?
  7. Are you using cookies or credentials? Then avoid * and use an exact origin.
  8. Are CORS headers also returned for error responses?
  9. Are you trying to fix CORS in frontend code? Move that logic to the backend.

In A Nutshell

CORS is a browser mechanism that controls whether JavaScript from one origin may read responses from another origin.

It exists because browsers need to protect users from malicious cross-origin data access.

To allow cross-origin requests, the backend must return the right CORS response headers.

And most importantly: CORS is useful, but it is not your complete API security system.

It controls browser access to responses. It does not replace authentication, authorization or proper backend security.

Learn More

These resources are great next steps if you want to go deeper: