DTTooleras

Understanding CORS: Why It Exists and How to Fix Common Errors

A practical guide to Cross-Origin Resource Sharing (CORS) — why browsers block requests, how preflight works, and step-by-step fixes for every common CORS error.

DevToolsHub Team18 min read1,054 words

What is CORS?

Cross-Origin Resource Sharing (CORS) is a security mechanism built into web browsers that controls how web pages can request resources from a different domain (origin) than the one that served the page.

An origin is defined by three parts:

  • Protocol (http vs https)
  • Domain (example.com)
  • Port (80, 443, 3000, etc.)

If any of these differ between the page making the request and the server receiving it, it's a cross-origin request.

Same origin:
  https://example.com/page → https://example.com/api ✅

Cross-origin:
  https://example.com → https://api.example.com ❌ (different subdomain)
  https://example.com → http://example.com ❌ (different protocol)
  http://localhost:3000 → http://localhost:8080 ❌ (different port)

Why Does CORS Exist?

Without CORS, any website could make requests to any other website using your browser's cookies and credentials. Imagine visiting a malicious site that silently makes requests to your bank's API using your session cookies — that's exactly what CORS prevents.

The Same-Origin Policy is the default browser security model: scripts can only make requests to the same origin. CORS is the mechanism that allows servers to opt in to cross-origin requests when they want to.

Key insight: CORS is enforced by the browser, not the server. The server sends CORS headers, and the browser decides whether to allow the response. This is why the same request works in Postman or curl but fails in the browser.

How CORS Works

Simple Requests

For "simple" requests (GET, POST with certain content types), the browser sends the request directly and checks the response headers:

Browser → Server:
  GET /api/data HTTP/1.1
  Origin: https://myapp.com

Server → Browser:
  HTTP/1.1 200 OK
  Access-Control-Allow-Origin: https://myapp.com

Browser: Origin matches → allow response ✅

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

  • Method is GET, HEAD, or POST
  • Only uses headers: Accept, Accept-Language, Content-Language, Content-Type
  • Content-Type is: application/x-www-form-urlencoded, multipart/form-data, or text/plain

Preflight Requests

For "non-simple" requests (PUT, DELETE, custom headers, JSON content type), the browser sends a preflight OPTIONS request first:

Step 1 — Preflight:
  Browser → Server:
    OPTIONS /api/data HTTP/1.1
    Origin: https://myapp.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: Content-Type, Authorization

  Server → Browser:
    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: https://myapp.com
    Access-Control-Allow-Methods: GET, PUT, DELETE
    Access-Control-Allow-Headers: Content-Type, Authorization
    Access-Control-Max-Age: 86400

Step 2 — Actual request (only if preflight succeeds):
  Browser → Server:
    PUT /api/data HTTP/1.1
    Origin: https://myapp.com
    Content-Type: application/json
    Authorization: Bearer token123

    {"name": "updated"}

  Server → Browser:
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://myapp.com

CORS Response Headers

HeaderPurposeExample
Access-Control-Allow-OriginWhich origins can accesshttps://myapp.com or *
Access-Control-Allow-MethodsAllowed HTTP methodsGET, POST, PUT, DELETE
Access-Control-Allow-HeadersAllowed request headersContent-Type, Authorization
Access-Control-Allow-CredentialsAllow cookies/authtrue
Access-Control-Expose-HeadersHeaders the browser can readX-Total-Count
Access-Control-Max-AgeCache preflight (seconds)86400

Common CORS Errors and Fixes

Error 1: "No Access-Control-Allow-Origin header"

Access to fetch at 'https://api.example.com/data' from origin
'https://myapp.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

Fix: Add the header on your server:

// Express.js
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "https://myapp.com");
  next();
});

// Or use the cors middleware
const cors = require("cors");
app.use(cors({ origin: "https://myapp.com" }));

Error 2: "Wildcard with credentials"

Access to fetch has been blocked: The value of the
'Access-Control-Allow-Origin' header must not be the wildcard '*'
when the request's credentials mode is 'include'.

Fix: You can't use * with credentials: "include". Specify the exact origin:

app.use(cors({
  origin: "https://myapp.com",  // Not "*"
  credentials: true,
}));

Error 3: "Method not allowed"

Method PUT is not allowed by Access-Control-Allow-Methods

Fix: Add the method to allowed methods:

app.use(cors({
  origin: "https://myapp.com",
  methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
}));

Error 4: "Header not allowed"

Request header field authorization is not allowed by
Access-Control-Allow-Headers

Fix: Add the header to allowed headers:

app.use(cors({
  origin: "https://myapp.com",
  allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
}));

Error 5: Preflight fails with 404 or 405

Your server doesn't handle OPTIONS requests.

Fix: Handle OPTIONS explicitly or use middleware:

// Express — handle OPTIONS for all routes
app.options("*", cors());

// Or handle it manually
app.options("/api/*", (req, res) => {
  res.header("Access-Control-Allow-Origin", "https://myapp.com");
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  res.sendStatus(204);
});

Server Configuration Examples

Express.js

const cors = require("cors");

// Allow all origins (development only!)
app.use(cors());

// Allow specific origin
app.use(cors({
  origin: "https://myapp.com",
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));

// Allow multiple origins
app.use(cors({
  origin: ["https://myapp.com", "https://admin.myapp.com"],
}));

// Dynamic origin
app.use(cors({
  origin: (origin, callback) => {
    const allowedOrigins = ["https://myapp.com", "https://admin.myapp.com"];
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
}));

Next.js API Routes

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Origin", value: "https://myapp.com" },
          { key: "Access-Control-Allow-Methods", value: "GET, POST, PUT, DELETE" },
          { key: "Access-Control-Allow-Headers", value: "Content-Type, Authorization" },
        ],
      },
    ];
  },
};

Nginx

location /api/ {
    add_header Access-Control-Allow-Origin "https://myapp.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Max-Age 86400 always;

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

Development Workarounds

During development, you often need to bypass CORS:

1. Proxy in Development

// Next.js — next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: "/api/:path*",
        destination: "https://api.example.com/:path*",
      },
    ];
  },
};

// Vite — vite.config.js
export default {
  server: {
    proxy: {
      "/api": {
        target: "https://api.example.com",
        changeOrigin: true,
      },
    },
  },
};

2. Browser Extension (Development Only)

Extensions like "CORS Unblock" disable CORS in the browser. Never use in production and never ask users to install one.

Security Best Practices

  1. Never use Access-Control-Allow-Origin: * with credentials — It's a security vulnerability
  2. Whitelist specific origins — Don't reflect the Origin header blindly
  3. Limit allowed methods and headers — Only allow what your API actually needs
  4. Set Access-Control-Max-Age — Reduces preflight requests (cache for 24 hours: 86400)
  5. Don't disable CORS in production — Fix the configuration instead
  6. Validate the Origin header server-side — Don't trust it blindly for security decisions
corscors errorcross-origincors fixaccess-control-allow-originpreflightcors javascript

Related articles

All articles

Practice with free tools

200+ free developer tools that run in your browser.

Browse all tools →