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:
httpsvshttp - Host:
api.example.comvsexample.com - Port:
:3000vs: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 A | URL B | Same Origin? | Reason |
|---|---|---|---|
| https://example.com/page | https://example.com/api | Yes | Same scheme, host, port |
| https://example.com | http://example.com | No | Different scheme |
| https://example.com | https://api.example.com | No | Different host |
| https://example.com:443 | https://example.com:8443 | No | Different 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.
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, ortext/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.
# 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: 86400CORS Response Headers
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Specifies which origin(s) can access the resource. Use * for public APIs or a specific origin for restricted access. |
Access-Control-Allow-Methods | Lists HTTP methods allowed for cross-origin requests |
Access-Control-Allow-Headers | Lists request headers allowed in cross-origin requests |
Access-Control-Allow-Credentials | Whether cookies/auth credentials can be included. Must be true explicitly (cannot be used with wildcard origin). |
Access-Control-Max-Age | How long (in seconds) the preflight response can be cached |
Access-Control-Expose-Headers | Response headers that browsers are allowed to access from JavaScript |
Configuring CORS
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
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,
}));*) 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.