CORS Explained: Cross-Origin Resource Sharing Guide

CORS is a browser security mechanism that controls how web pages can request resources from a different domain. Misconfigured CORS causes errors and security vulnerabilities.


If you have ever seen an error like "Access to fetch at 'https://api.example.com' from origin 'https://app.example.com' has been blocked by CORS policy", you have encountered CORS. Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls when and how web pages can make requests to a different origin than the one that served them.

What Is the Same-Origin Policy?

Before CORS, browsers enforced the same-origin policy — a fundamental security restriction that prevents web pages from making requests to a different origin. An "origin" is the combination of:

  • Scheme: https vs http
  • Host: api.example.com vs example.com
  • Port: :3000 vs :8080

Two URLs are same-origin only if all three match exactly. https://example.com and https://api.example.com are cross-origin because the hosts differ.

URL AURL BSame Origin?Reason
https://example.com/pagehttps://example.com/apiYesSame scheme, host, port
https://example.comhttp://example.comNoDifferent scheme
https://example.comhttps://api.example.comNoDifferent host
https://example.com:443https://example.com:8443NoDifferent port

What Is CORS?

CORS is a mechanism that allows servers to relax the same-origin policy in a controlled way. Instead of blocking all cross-origin requests, a server can explicitly declare which origins are allowed to access its resources by including CORS response headers.

The browser enforces CORS — not the server. When a page makes a cross-origin request, the browser checks whether the server's CORS headers permit it. If not, the browser blocks the response even if the server sent it.

CORS is a browser restrictionCORS applies only to browser-based requests. Server-to-server requests (e.g., from Node.js, Python, curl) are not subject to CORS. This is why an API call works fine from curl but fails in a browser.

Simple Requests vs Preflight Requests

CORS distinguishes between two types of cross-origin requests:

Simple Requests

A request is considered "simple" if it meets all of these criteria:

  • Method is GET, HEAD, or POST.
  • Only standard headers are used (Accept, Accept-Language, Content-Language, Content-Type).
  • Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain.

For simple requests, the browser sends the request directly and checks the CORS headers in the response.

Preflight Requests

For requests that are not "simple" (e.g., JSON POST, PUT, DELETE, or requests with custom headers), the browser first sends an OPTIONS preflight request to ask the server if the actual request is allowed. Only if the preflight response approves does the browser send the actual request.

http
# Preflight request OPTIONS /api/data HTTP/1.1 Host: api.example.com Origin: https://app.example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization # Preflight response HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400

CORS Response Headers

HeaderPurpose
Access-Control-Allow-OriginSpecifies which origin(s) can access the resource. Use * for public APIs or a specific origin for restricted access.
Access-Control-Allow-MethodsLists HTTP methods allowed for cross-origin requests
Access-Control-Allow-HeadersLists request headers allowed in cross-origin requests
Access-Control-Allow-CredentialsWhether cookies/auth credentials can be included. Must be true explicitly (cannot be used with wildcard origin).
Access-Control-Max-AgeHow long (in seconds) the preflight response can be cached
Access-Control-Expose-HeadersResponse headers that browsers are allowed to access from JavaScript

Configuring CORS

Nginx

nginx
location /api/ { add_header Access-Control-Allow-Origin "https://app.example.com"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type, Authorization"; add_header Access-Control-Max-Age 86400; if ($request_method = 'OPTIONS') { return 204; } }

Node.js / Express

javascript
const cors = require('cors'); app.use(cors({ origin: 'https://app.example.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 86400, }));
Avoid Access-Control-Allow-Origin: *Using a wildcard (*) allows any origin to access your API. This is fine for truly public, read-only APIs, but should not be used for APIs that handle authenticated data. When credentials (Access-Control-Allow-Credentials: true) are needed, a wildcard origin is not permitted — you must specify the exact origin.

Common CORS Errors and Fixes

Error: No Access-Control-Allow-Origin header

The server is not sending any CORS headers. Add Access-Control-Allow-Origin to the response for the requested route.

Error: The CORS header 'Access-Control-Allow-Origin' does not match

The server sends a CORS header, but the allowed origin doesn't match the requesting origin. Ensure the Access-Control-Allow-Origin value exactly matches the requesting origin (case-sensitive, including scheme).

Error: Preflight request doesn't pass access control check

The OPTIONS preflight is not handled. Ensure your server responds to OPTIONS requests with appropriate CORS headers and a 200 or 204 status. Many frameworks require explicit configuration for preflight routes.

Checking CORS Headers

Use the ShowDNS CORS Tester to check what CORS headers your server returns and whether your API is correctly configured for cross-origin access.

Frequently Asked Questions

Can CORS prevent all cross-origin attacks?

No. CORS prevents unauthorized JavaScript from reading cross-origin responses. It does not prevent cross-origin requests from being made (the request still reaches the server — the browser just blocks the JavaScript from reading the response). For write-side protection, use CSRF tokens.

Why does my CORS request work with curl but fail in the browser?

curl and other server-side tools do not enforce CORS — they just make HTTP requests without the browser's security restrictions. CORS is entirely a browser enforcement mechanism.

What does Access-Control-Allow-Origin: * mean?

It means any website on the internet can read the response from your API. This is appropriate for public APIs that serve non-sensitive data (like a public CDN or weather API). It is not appropriate for APIs that serve user-specific data or accept credentials.

Related Articles