Ctrl + K
Security18 min read

CORS Explained

A practical guide to CORS: same-origin policy, origins, simple and preflighted requests, CORS headers, credentials, common errors, security considerations and API configuration.

Published: 2026-10-05

CORS, or Cross-Origin Resource Sharing, is a browser security mechanism that controls whether a web page can make certain requests to a different origin and access the response. It is one of the most common sources of confusion when working with frontend applications and REST APIs.

A frontend running at https://app.example.com may need to request data from https://api.example.com. These URLs have different origins because their hostnames are different. The browser therefore applies the same-origin policy and requires the API to explicitly allow the requesting origin when the response is accessed by browser JavaScript.

CORS is implemented primarily through HTTP request and response headers. The browser sends information about the request's origin, the server responds with the appropriate CORS headers, and the browser decides whether the frontend JavaScript is allowed to access the response.

What Is CORS?

CORS stands for Cross-Origin Resource Sharing. It is a mechanism that allows a server to specify which cross-origin browser requests are permitted.

The important detail is that CORS is primarily enforced by browsers. A server can receive a request from another origin regardless of whether it has configured CORS, but the browser may prevent the calling web page from reading the response.

Origin: https://app.example.com

The Origin request header tells the server which origin initiated the browser request. The server can then return an Access-Control-Allow-Origin response header if that origin is permitted.

Access-Control-Allow-Origin: https://app.example.com

Why Does CORS Exist?

Browsers implement the same-origin policy to prevent a web page from freely reading data from unrelated origins. Without such restrictions, a malicious website could potentially use a user's authenticated browser session to make requests to other websites and read their private responses.

For example, imagine that a user is logged in to an online service. If any website could freely make authenticated requests to that service and read the responses, a malicious page could potentially access information that the user never intended to share with it.

CORS provides a controlled mechanism for servers to say which other origins are allowed to access their resources from browser-based applications.

What Is an Origin?

An origin consists of three components: scheme, host and port.

https://example.com:443
ComponentExample
Schemehttps
Hostexample.com
Port443

Two URLs have the same origin only when their scheme, host and port all match according to the URL and origin rules.

Same-Origin vs Cross-Origin

URLCompared with https://example.comReason
https://example.com/apiSame-originSame scheme, host and port.
https://api.example.comCross-originDifferent host.
http://example.comCross-originDifferent scheme.
https://example.com:8443Cross-originDifferent port.

A different path does not create a different origin. For example, https://example.com and https://example.com/api have the same origin.

CORS Is Not a Server-to-Server Security Mechanism

One of the most important CORS concepts is that CORS is a browser-enforced mechanism. It does not generally prevent a command-line client, backend service or other non-browser HTTP client from sending a request to an API.

For example, a server can receive an HTTP request from curl without the browser CORS enforcement that applies to frontend JavaScript. Therefore, CORS should not be treated as authentication or as a general API access-control mechanism.

⚠️ CORS does not replace authentication, authorization, CSRF protection or server-side access control. It controls whether browser JavaScript is allowed to access a cross-origin response.

The Origin Request Header

When a browser makes a relevant cross-origin request, it can include the Origin request header.

Origin: https://app.example.com

The value identifies the origin that initiated the request. The server can use this information when deciding which CORS response headers to return.

Access-Control-Allow-Origin

Access-Control-Allow-Origin is the central CORS response header. It tells the browser which origin is allowed to access the response.

Access-Control-Allow-Origin: https://app.example.com

A server can also use the wildcard value for resources that are intentionally available to any origin under the applicable CORS rules.

Access-Control-Allow-Origin: *

The wildcard should not be treated as a universal replacement for an explicit origin. In particular, credentialed browser requests have additional restrictions.

How a Basic CORS Request Works

Suppose a frontend application is hosted at https://app.example.com and requests a resource from https://api.example.com.

GET /users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com

If the API allows that origin, it can return:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json

The browser checks the response and allows the frontend code to access the response when the CORS policy permits it.

Simple Requests

Some cross-origin requests can be sent without a separate CORS preflight. These are commonly described as simple requests when they satisfy the relevant conditions for method, request headers and Content-Type.

The commonly permitted methods for a simple request are GET, HEAD and POST. The request also needs to avoid disallowed author request headers and use an allowed Content-Type when a Content-Type header is present.

Content-TypeSimple-request value
text/plainYes
application/x-www-form-urlencodedYes
multipart/form-dataYes
application/jsonNot a simple-request Content-Type

This is why a frontend POST request using application/json often triggers a preflight even though POST itself is one of the methods that can be used by a simple request.

What Is a CORS Preflight Request?

A preflight request is an HTTP OPTIONS request that a browser sends before certain cross-origin requests. Its purpose is to ask the server whether the actual request is permitted.

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

The server can respond with the methods and headers that it permits.

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization

If the response satisfies the browser's CORS requirements, the browser can proceed with the actual request.

Why Does POST application/json Trigger Preflight?

A common example is a frontend sending JSON to an API:

fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "Alex" })
})

Although POST is allowed for simple requests, application/json is not one of the simple Content-Type values. The browser therefore commonly performs a preflight before sending the actual POST request.

Access-Control-Allow-Methods

Access-Control-Allow-Methods tells the browser which HTTP methods are permitted for the relevant CORS request.

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

The server should expose the methods that the application actually needs rather than treating a broad list as a security requirement.

Access-Control-Allow-Headers

Access-Control-Allow-Headers tells the browser which request headers can be used by the actual cross-origin request when the browser performs a preflight.

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

This is especially relevant for APIs that use JSON and Authorization headers.

Access-Control-Expose-Headers

By default, browser JavaScript cannot necessarily read every response header from a cross-origin response. Access-Control-Expose-Headers allows a server to expose additional response headers to frontend code.

Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining

This is useful when an API returns information in custom response headers that the frontend needs to read.

CORS and Credentials

Credentials can include cookies and certain other browser-managed credentials. Credentialed cross-origin requests require additional CORS configuration.

fetch("https://api.example.com/profile", {
  credentials: "include"
})

The server must explicitly allow credentials with the Access-Control-Allow-Credentials response header.

Access-Control-Allow-Credentials: true

For credentialed requests, the server also needs an appropriate explicit Access-Control-Allow-Origin value. The wildcard value is not valid for granting credentialed access.

⚠️ Do not combine Access-Control-Allow-Credentials: true with Access-Control-Allow-Origin: * when the goal is to permit credentialed browser requests. Use an explicit allowed origin instead.

CORS and Cookies

Cookies introduce another layer of browser security rules. Even if CORS allows a frontend to access the response, cookie behavior can still be affected by cookie attributes such as SameSite, Secure and Domain.

For cross-site cookie scenarios, SameSite policy is especially important. CORS does not override cookie restrictions imposed by the browser.

CORS with Authorization Headers

A frontend may send a bearer token using the Authorization request header.

Authorization: Bearer eyJ...

Because Authorization is not a simple request header, a request using it can require a preflight. The server should therefore allow Authorization in Access-Control-Allow-Headers when the application legitimately needs it.

Access-Control-Max-Age

Access-Control-Max-Age tells the browser how long the result of a preflight request may be cached.

Access-Control-Max-Age: 600

A suitable value can reduce the number of OPTIONS requests that browsers need to make. The browser may impose its own limits on how long preflight results are cached.

The Vary: Origin Header

When a server dynamically returns Access-Control-Allow-Origin based on the request's Origin header, caches need to know that the response varies according to Origin.

Access-Control-Allow-Origin: https://app.example.com
Vary: Origin

Vary: Origin is important when shared caches or CDNs are involved. Without an appropriate cache policy, a response generated for one origin could potentially be reused incorrectly for another origin.

CORS Errors in the Browser

A browser may display an error similar to:

Access to fetch at 'https://api.example.com/users' from origin 'https://app.example.com' has been blocked by CORS policy.

This message means that the browser's CORS checks did not allow the frontend JavaScript to access the response. The exact cause can be different depending on the request.

Common CORS Error Causes

  • The server does not return Access-Control-Allow-Origin.
  • The returned origin does not match the requesting origin.
  • A preflight response does not allow the requested method.
  • A preflight response does not allow a requested request header.
  • Credentials are used without Access-Control-Allow-Credentials.
  • Credentials are combined with an incompatible wildcard origin policy.
  • An OPTIONS request is rejected by a proxy, firewall or application.
  • A CDN or cache serves a CORS response for the wrong origin.
  • The server redirects the request in a way that conflicts with the browser's CORS processing.

CORS and HTTP OPTIONS

OPTIONS is an HTTP method used to discover communication options for a target resource. Browsers use it for CORS preflight requests.

A common backend mistake is to configure GET and POST routes correctly but forget that the API infrastructure also needs to handle the browser's OPTIONS request.

OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
💡 If a request works with curl but fails in a browser, inspect the browser's OPTIONS request. A missing or incorrectly handled preflight is a common cause.

CORS and Redirects

Redirects can make CORS debugging more complicated because the browser has to apply the relevant security and CORS rules throughout the request process. Authentication redirects, HTTP-to-HTTPS redirects and trailing-slash redirects can therefore appear as part of a CORS problem.

When debugging a failed cross-origin request, inspect the complete request chain rather than looking only at the final URL.

CORS Does Not Mean the API Is Public

Allowing a frontend origin through CORS does not mean that the API has become publicly accessible in the general sense. It means that browser JavaScript from the allowed origin can access the response under the configured CORS rules.

A public API can still require authentication. Conversely, an API with strict CORS can still receive requests from non-browser clients. Authentication and authorization must therefore be implemented independently.

CORS Is Not Authentication

Authentication answers the question of who a user or client is. Authorization answers what that identity is allowed to do. CORS answers whether browser JavaScript from an origin may access a cross-origin response.

MechanismMain purpose
CORSControls browser access to cross-origin responses.
AuthenticationEstablishes the identity of a user or client.
AuthorizationDetermines what an authenticated identity may access.
CSRF protectionProtects state-changing actions against cross-site request attacks.

CORS vs CSRF

CORS and CSRF address different security problems. CORS controls whether frontend JavaScript can access cross-origin responses. CSRF concerns unwanted state-changing requests made using a user's existing authentication context.

A server should not assume that a CORS configuration automatically provides CSRF protection. Applications using cookie-based authentication should consider appropriate CSRF defenses as part of their overall security design.

Wildcard CORS

A wildcard policy looks simple:

Access-Control-Allow-Origin: *

It can be appropriate for genuinely public resources that do not require credentialed browser access. However, using a wildcard everywhere can be unnecessarily broad and can conflict with credentialed requests.

If an API has a known set of frontend applications, explicitly allowing those origins can provide a more controlled configuration.

Dynamic Origin Allow Lists

Applications with multiple frontend environments may maintain an allow list of origins and dynamically return the matching origin.

const allowedOrigins = [
  "https://app.example.com",
  "https://admin.example.com"
];

const origin = request.headers.get("Origin");

if (origin && allowedOrigins.includes(origin)) {
  response.headers.set("Access-Control-Allow-Origin", origin);
  response.headers.set("Vary", "Origin");
}

The exact implementation depends on the server framework. The important principle is to compare the incoming Origin against an explicit allow list rather than blindly reflecting arbitrary input.

Do Not Blindly Reflect the Origin

A common mistake is to read the Origin request header and always return that same value in Access-Control-Allow-Origin without validating it.

Access-Control-Allow-Origin: <incoming Origin>
⚠️ If the application intends to restrict access to specific origins, the incoming Origin must be checked against an allow list or another explicit policy. Reflecting arbitrary origins can defeat the intended restriction.

CORS Configuration in Development

Local development often introduces origins such as http://localhost:3000 or http://localhost:5173. These are different origins from a production HTTPS domain.

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

A development allow list can therefore contain local origins while production configuration contains only the origins that actually need access.

⚠️ Avoid copying a development policy such as Access-Control-Allow-Origin: * into production simply because it makes local development easier.

CORS with a REST API

A REST API used by a browser frontend commonly needs to handle GET requests, JSON POST requests, authentication headers and OPTIONS preflight requests.

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600

The exact set of headers should match the application's actual requirements. There is no need to expose every method and header if the frontend does not use them.

CORS and Reverse Proxies

CORS headers can be added or modified by the application server, reverse proxy, API gateway or CDN. This can make debugging confusing because the application code may appear correct while an infrastructure layer changes the final response.

When investigating a CORS issue, inspect the response that actually reaches the browser. The final HTTP headers matter more than what a backend configuration file appears to intend.

CORS and CDNs

Caching adds another consideration when CORS headers depend on Origin. If a CDN caches a response for one origin and reuses it for another without accounting for Origin, the resulting response may have incorrect CORS metadata.

Access-Control-Allow-Origin: https://app.example.com
Vary: Origin

When configuring CORS behind a CDN or shared cache, verify the cache key and Vary behavior as well as the application-level CORS configuration.

CORS Headers at a Glance

HeaderPurpose
OriginIdentifies the requesting origin.
Access-Control-Allow-OriginSpecifies which origin may access the response.
Access-Control-Allow-MethodsSpecifies permitted methods for relevant CORS requests.
Access-Control-Allow-HeadersSpecifies permitted request headers for relevant preflighted requests.
Access-Control-Allow-CredentialsAllows credentialed browser requests when set appropriately.
Access-Control-Expose-HeadersExposes additional response headers to browser JavaScript.
Access-Control-Max-AgeControls caching of preflight results.
Access-Control-Request-MethodUsed by a preflight to indicate the intended method.
Access-Control-Request-HeadersUsed by a preflight to indicate intended request headers.

How to Debug CORS Step by Step

CORS errors are much easier to solve when you inspect the actual HTTP exchange instead of changing random server settings.

  • Open the browser's Network panel.
  • Find the request that failed.
  • Check the Request Headers and find Origin.
  • If an OPTIONS request exists, inspect it first.
  • Check Access-Control-Request-Method.
  • Check Access-Control-Request-Headers.
  • Inspect the OPTIONS response status code.
  • Check Access-Control-Allow-Origin.
  • Check Access-Control-Allow-Methods when applicable.
  • Check Access-Control-Allow-Headers when applicable.
  • Check Access-Control-Allow-Credentials for credentialed requests.
  • Inspect redirects and CDN or proxy responses.
  • Compare the actual response headers with the frontend request.

A Practical CORS Checklist

  • Know the exact frontend origin.
  • Remember that scheme, host and port are part of the origin.
  • Return an appropriate Access-Control-Allow-Origin value.
  • Handle OPTIONS requests when preflight is required.
  • Allow only the HTTP methods the frontend actually needs.
  • Allow only the request headers the frontend actually needs.
  • Configure credentials explicitly when cookies or other credentials are required.
  • Do not use a wildcard origin for credentialed requests.
  • Use Vary: Origin when the response varies by Origin and shared caching is involved.
  • Remember that CORS does not replace authentication or authorization.
  • Keep development and production origin policies separate.
  • Inspect the final response headers when debugging.

Frequently Asked Questions

What is CORS in simple terms?

CORS is a browser security mechanism that lets a server specify which other origins are allowed to access its responses from browser JavaScript.

Why does CORS happen if both applications use HTTPS?

HTTPS alone does not make two URLs same-origin. If their hosts or ports differ, they can still be cross-origin and browser CORS rules can apply.

What is a CORS preflight?

A preflight is an OPTIONS request that a browser sends before certain cross-origin requests to ask the server whether the intended method and request headers are permitted.

Why does a POST request trigger a CORS preflight?

POST itself can be used by a simple request, but the complete request may not qualify as simple. For example, application/json is not one of the simple Content-Type values, so a preflight is commonly required.

Is CORS a security mechanism for an API?

CORS is a browser security mechanism, but it is not authentication or authorization. Non-browser clients can generally send HTTP requests without browser CORS enforcement.

Can I use Access-Control-Allow-Origin: *?

Yes, when the resource is intentionally accessible from any origin under the applicable CORS rules. However, wildcard origin cannot be used to grant credentialed browser access.

Why does my request work in Postman but fail in the browser?

Postman is not subject to browser CORS enforcement in the same way as frontend JavaScript. The API may therefore respond successfully to Postman while the browser blocks JavaScript from accessing the response.

Helpful CORS and HTTP Tools

A CORS Header Generator can help construct the headers needed for common cross-origin configurations. An HTTP Header Generator is useful when building a complete response-header set, while an HTTP Header Viewer can help inspect the headers actually returned by an API.

An HTTP Request Builder can be useful for reproducing requests with specific methods and headers during debugging. A REST API Mock Generator can also help create a controlled API endpoint for testing frontend CORS behavior without changing a production service.

Conclusion

CORS exists because browsers enforce the same-origin policy and do not allow frontend JavaScript to freely read responses from unrelated origins. Servers can selectively relax that restriction by returning the appropriate CORS response headers.

The most important concepts are Origin, Access-Control-Allow-Origin, preflight OPTIONS requests, Access-Control-Allow-Methods, Access-Control-Allow-Headers and credential handling. Once these pieces are understood, most CORS errors become ordinary HTTP debugging problems rather than mysterious browser failures.

CORS should also be kept separate from authentication, authorization and CSRF protection. A correct CORS configuration controls browser access to cross-origin responses, while the API still needs its own security model for deciding who can access or modify data.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.