Troubleshooting

How to Fix Cross-Origin Resource Sharing (CORS) Issues

Cross-Origin Resource Sharing (CORS) errors occur when a browser blocks a web page from making requests to a different origin (scheme, host, or port) than the one that served the page. This is enforced by the browser’s same-origin policy to protect users. CORS is a protocol that allows servers to explicitly say which Cross-origin requests are permitted. If the server doesn’t respond with the right CORS headers, the browser refuses to deliver the response to JavaScript, resulting in errors.


Overview of the Problem

CORS sits on top of the browser’s same-origin policy. A request is cross-origin if any of the following differ between the page and the requested resource:

  • Scheme: http vs https
  • Hostname: api.example.com vs app.example.com
  • Port: :80 vs :3000

When a cross-origin request occurs, the browser checks if it is a “simple request” (GET/HEAD/POST with certain safe headers and content types) or if it requires a “preflight” (an OPTIONS request sent before the actual request). The preflight asks the server whether the real request is allowed. If the server replies with the necessary Access-Control-* headers, the browser proceeds. Otherwise, it blocks.

Common console errors:

  • “Access to fetch at ‘https://api.example.com‘ from origin ‘https://app.example.com‘ has been blocked by CORS policy…”
  • “Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present…”

Possible Causes

  • Missing or incorrect CORS response headers on the server
  • Preflight (OPTIONS) request not handled or blocked
  • Using credentials (cookies/Authorization) without Access-Control-Allow-Credentials: true
  • Using wildcard (*) with credentials, which is disallowed
  • Mis-specified Access-Control-Allow-Headers or Access-Control-Allow-Methods
  • Redirects stripping or not returning CORS headers
  • CDN or cache not varying responses by Origin (missing Vary: Origin)
  • Mixed content (https page calling http API) or blocked by corporate proxy
  • Development proxy not configured or forwarding headers incorrectly

Step-by-Step Troubleshooting Guide

1) Reproduce and Read the Error Clearly

  • Open the browser devtools (Network + Console).
  • Find the failed request. Look for the preflight (OPTIONS) request if present.
  • Note the error message and which header is missing or incorrect.

Typical errors:

  • “No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”
  • “The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’.”
  • “Request header field X-My-Header is not allowed by Access-Control-Allow-Headers in preflight response.”
See also  How to Fix ColdFusion Administrator Crashing

Request URL: https://api.example.com/data
Request Method: OPTIONS
Status Code: 200
Response Headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400


2) Identify the Request Characteristics

  • Is it a “simple” request? If not, it triggers a preflight.
  • Check method (GET/POST/PUT/DELETE), headers (Authorization, X-Requested-With, custom headers), and Content-Type.
  • Are you sending credentials? fetch(…, { credentials: ‘include’ }) or XHR with withCredentials = true will require Access-Control-Allow-Credentials: true and a non-wildcard origin.

Preflight triggers include:

  • Methods other than GET, HEAD, POST
  • Custom headers (e.g., X-*, Authorization)
  • Content-Type not in: application/x-www-form-urlencoded, multipart/form-data, text/plain

3) Inspect the Server’s CORS Response

For successful Cross-origin requests, the server should return:

  • Access-Control-Allow-Origin: or *
  • Access-Control-Allow-Methods: e.g., GET, POST, PUT, DELETE, OPTIONS
  • Access-Control-Allow-Headers: list of request headers you expect (Content-Type, Authorization, etc.)
  • Access-Control-Allow-Credentials: true (only if using cookies/HTTP auth)
  • Access-Control-Max-Age: seconds to cache preflight (optional but useful)
  • Vary: Origin (critical if the origin is echoed dynamically)

Important:

  • If credentials are used, do not use “*”. Use the exact origin value and include Access-Control-Allow-Credentials: true.
  • Ensure your server returns these headers both on preflight (OPTIONS) and on the actual request.

4) Implement or Fix CORS on the Server

Below are common server configurations. Only implement CORS on the backend or a trusted Reverse proxy/gateway—never “fix” CORS purely in frontend code.

Node.js (Express) with cors:
js
import express from ‘express’;
import cors from ‘cors’;

const app = express();

const allowedOrigins = [‘https://app.example.com‘, ‘http://localhost:3000‘];
app.use(cors({
origin: (origin, cb) => {
// allow no Origin for same-origin or curl
if (!origin) return cb(null, true);
if (allowedOrigins.includes(origin)) return cb(null, true);
return cb(new Error(‘Not allowed by CORS’));
},
methods: [‘GET’, ‘POST’, ‘PUT’, ‘PATCH’, ‘DELETE’, ‘OPTIONS’],
allowedHeaders: [‘Content-Type’, ‘Authorization’],
credentials: true,
maxAge: 86400
}));

app.options(‘*’, cors()); // Ensure preflight handled

Nginx (Reverse proxy):

location /api/ {
proxy_pass http://backend;

Echo Origin safely

if ($http_origin ~* ^https?://(app.example.com|localhost:3000)$) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Vary Origin always;
add_header Access-Control-Allow-Methods “GET, POST, PUT, PATCH, DELETE, OPTIONS” always;
add_header Access-Control-Allow-Headers “Authorization, Content-Type” always;
add_header Access-Control-Allow-Credentials “true” always;
add_header Access-Control-Max-Age “86400” always;
}

if ($request_method = OPTIONS) {
return 204;
}
}

Apache (.htaccess or vhost):


SetEnvIf Origin “https?://(app\.example\.com|localhost:3000)$” AccessControlAllowOrigin=$0
Header always set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header always set Vary “Origin”
Header always set Access-Control-Allow-Methods “GET, POST, PUT, PATCH, DELETE, OPTIONS”
Header always set Access-Control-Allow-Headers “Authorization, Content-Type”
Header always set Access-Control-Allow-Credentials “true”
Header always set Access-Control-Max-Age “86400”

Python (Flask) with flask-cors:
python
from flask import Flask
from flask_cors import CORS

app = Flask(name)
CORS(app,
origins=[“https://app.example.com“, “http://localhost:3000“],
supports_credentials=True,
allow_headers=[“Content-Type”, “Authorization”],
methods=[“GET”,”POST”,”PUT”,”PATCH”,”DELETE”,”OPTIONS”],
max_age=86400)

Spring Boot (Java):
java
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping(“/**”)
.allowedOrigins(“https://app.example.com“, “http://localhost:3000“)
.allowedMethods(“GET”,”POST”,”PUT”,”PATCH”,”DELETE”,”OPTIONS”)
.allowedHeaders(“Authorization”,”Content-Type”)
.allowCredentials(true)
.maxAge(86400);
}
};
}
}

ASP.NET Core:
csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
options.AddPolicy(“AllowApp”,
policy => policy
.WithOrigins(“https://app.example.com“, “http://localhost:3000“)
.AllowAnyMethod()
.AllowCredentials()
.WithHeaders(“Authorization”,”Content-Type”));
});

var app = builder.Build();
app.UseCors(“AllowApp”);
app.MapControllers();
app.Run();

Amazon S3 bucket CORS (JSON):
json
[
{
“AllowedHeaders”: [“Authorization”, “Content-Type”],
“AllowedMethods”: [“GET”, “PUT”, “POST”, “DELETE”, “HEAD”],
“AllowedOrigins”: [“https://app.example.com“, “http://localhost:3000“],
“ExposeHeaders”: [“ETag”, “x-amz-request-id”],
“MaxAgeSeconds”: 86400
}
]

See also  How to Resolve ColdFusion Startup Timeout Issues

Note: If you use CloudFront, ensure behaviors forward Origin and include Vary: Origin.


5) Ensure Preflight OPTIONS Is Reachable

  • Some servers require explicit routing for OPTIONS.
  • Do not require Authentication for OPTIONS preflight.
  • Return a 2xx with appropriate CORS headers.

Example Express handler if you’re not using a library:
js
app.options(‘*’, (req, res) => {
const origin = req.headers.origin;
if ([‘https://app.example.com’,’http://localhost:3000′].includes(origin)) {
res.set(‘Access-Control-Allow-Origin’, origin);
res.set(‘Vary’, ‘Origin’);
res.set(‘Access-Control-Allow-Methods’, ‘GET,POST,PUT,PATCH,DELETE,OPTIONS’);
res.set(‘Access-Control-Allow-Headers’, ‘Authorization,Content-Type’);
res.set(‘Access-Control-Allow-Credentials’, ‘true’);
res.set(‘Access-Control-Max-Age’, ‘86400’);
return res.sendStatus(204);
}
res.sendStatus(403);
});


6) Handle Credentials Carefully

If you need cookies or HTTP auth:

  • Set fetch with credentials: ‘include’ or XHR with withCredentials = true.
  • On the server, set Access-Control-Allow-Credentials: true and a specific Access-Control-Allow-Origin (not *).
  • Cookies should be Secure, HttpOnly, and SameSite=None when cross-site.

Client example:
js
await fetch(‘https://api.example.com/me‘, {
credentials: ‘include’,
headers: { ‘Content-Type’: ‘application/json’ }
});


7) Dev Environment Workarounds (Proxies)

For local development, proxy frontend requests to the backend so the browser sees same-origin.

  • Vite:
    js
    // vite.config.js
    export default {
    server: {
    proxy: {
    ‘/api’: {
    target: ‘http://localhost:5000‘,
    changeOrigin: true
    }
    }
    }
    }

  • Create React App:
    json
    // package.json
    {
    “proxy”: “http://localhost:5000
    }

  • Webpack dev server:
    js
    devServer: {
    proxy: { ‘/api’: ‘http://localhost:5000‘ }
    }

These tools avoid CORS altogether during development by serving through the same origin.


Quick Cause / Solution Reference

  • Cause: Missing Access-Control-Allow-Origin
    • Solution: Return Access-Control-Allow-Origin with the requesting origin or * (no credentials).
  • Cause: Using credentials with wildcard origin
  • Cause: Preflight blocked or not handled
    • Solution: Implement OPTIONS route that returns Access-Control-Allow-Methods and Access-Control-Allow-Headers.
  • Cause: Custom header not allowed
    • Solution: Include header name in Access-Control-Allow-Headers.
  • Cause: Methods not allowed
    • Solution: Include method in Access-Control-Allow-Methods.
  • Cause: CDN/cache serving cached CORS headers for a different origin
    • Solution: Add Vary: Origin and configure CDN to forward Origin.
  • Cause: Redirects dropping CORS headers
    • Solution: Ensure all redirect targets include CORS headers or avoid redirects for API endpoints.
  • Cause: Mixed content (https page calling http API)
    • Solution: Serve API over https or use a proxy.

Common mistakes and How to Avoid Them

  • Using “fixes” in frontend only. Browsers enforce CORS; client-side code cannot bypass it. Fix it on the server or at a trusted proxy.
  • Sending Access-Control-Allow-Origin in request headers. These are response headers and must come from the server.
  • Wildcard with credentials. “*” is incompatible with Access-Control-Allow-Credentials: true.
  • Forgetting Vary: Origin. Without it, caches may serve the wrong headers to other origins.
  • Not allowing OPTIONS. Some auth middleware blocks OPTIONS; exempt it.
  • Incorrect Access-Control-Allow-Headers. If the request sends Authorization or a custom header, you must list it.
  • Only setting headers on 200 responses. CORS headers must also be present on errors (4xx/5xx) and on preflight responses.
  • Allowing everything in production. Restrict origins, methods, and headers to the minimum required.
  • Ignoring redirects. 30x targets must also reply with correct headers, or avoid redirect chains.
  • Assuming Postman equals browser behavior. Postman doesn’t enforce CORS; success in Postman doesn’t mean the browser will pass.

Prevention Tips / Best practices

  • Centralize cross-origin access at your API Gateway or reverse proxy; apply consistent CORS policy there.
  • Whitelist specific origins for production; avoid * except for public, non-credentialed resources.
  • Prefer “simple” requests when possible: avoid unnecessary custom headers and non-simple content types.
  • Cache preflights with Access-Control-Max-Age to reduce latency; ensure correctness before raising it.
  • Always set Vary: Origin when echoing the Origin value.
  • Test with automated Integration tests that simulate browser-like requests and verify CORS headers.
  • Document the contract: which origins, methods, and headers are allowed; share it with frontend teams.
  • For session cookies across sites, use SameSite=None; Secure; and enable Access-Control-Allow-Credentials with a specific origin.
  • Keep environments aligned: mirror CORS settings across dev, staging, and prod.
  • Log preflight requests and CORS decisions to aid Debugging.
See also  How to Fix Session Scope Losing Data Randomly

Example Logs and What to Look For

Server logs can reveal if the preflight is reaching the server and which Origin and headers are being requested.

Example access log:

OPTIONS /api/user HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type

If you don’t see this, a network device might be blocking OPTIONS, or the request is not reaching your server.

On failure, verify the response includes:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Allow-Credentials: true
Vary: Origin


Key Takeaways

  • CORS is a browser-enforced Security protocol; only the server (or a trusted proxy) can allow cross-origin requests.
  • Understand your request: method, headers, credentials, and whether it triggers a preflight.
  • Implement CORS headers on both preflight and actual responses; add Vary: Origin when echoing origins.
  • Credentials require Access-Control-Allow-Credentials: true and a non-wildcard Access-Control-Allow-Origin.
  • Use proxies in development to avoid CORS complexity and keep production policies strict and explicit.

FAQ

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

Postman is not a browser and doesn’t enforce the same-origin policy. Browsers block responses without the correct CORS headers. Your server must return proper Access-Control-* headers for browser-based clients.

How can I debug a failing preflight (OPTIONS) request?

Check the Network panel for the OPTIONS request. Confirm the server returns Access-Control-Allow-Methods and Access-Control-Allow-Headers that include what the browser asked for in Access-Control-Request-Method and Access-Control-Request-Headers. Ensure OPTIONS isn’t blocked by auth middleware or firewalls and returns a 2xx with headers.

Can I disable CORS in the browser to test?

You can run a browser with web Security disabled for local testing, but it’s unsafe and not representative of real users. Prefer a local dev proxy (Vite/webpack/CRA proxy) or configure CORS correctly on a Local server.

Do WebSockets and Server-Sent Events (SSE) use CORS?

WebSockets are not governed by CORS, but many servers enforce an Origin check; configure an allowlist. SSE and EventSource do follow CORS; ensure the server provides the correct Access-Control-Allow-Origin (and credentials settings if needed).

What if my API is behind CloudFront or another CDN?

Ensure the CDN forwards the Origin header to the origin server and adds Vary: Origin to cache keys. If the CDN adds CORS headers itself, configure rules to echo the request Origin safely and avoid serving mismatched headers from cache.

About the author

Aaron Longnion

Aaron Longnion

Hey there! I'm Aaron Longnion — an Internet technologist, web software engineer, and ColdFusion expert with more than 24 years of experience. Over the years, I've had the privilege of working with some of the most exciting and fast-growing companies out there, including lynda.com, HomeAway, landsofamerica.com (CoStar Group), and Adobe.com.

I'm a full-stack developer at heart, but what really drives me is designing and building internet architectures that are highly scalable, cost-effective, and fault-tolerant — solutions built to handle rapid growth and stay ahead of the curve.