DEV Community

Young Gao
Young Gao

Posted on

CORS Explained Simply: Why Your Frontend Can't Talk to Your API (Fix in 5 Minutes)

CORS Explained: Why Your Frontend Cannot Talk to Your API

Your React app calls your Express API. Chrome blocks the request. The error says "No Access-Control-Allow-Origin header." You add cors() and it works. But do you know what actually happened?

What CORS Is

Cross-Origin Resource Sharing. Browsers block requests from one origin (domain:port) to another by default. CORS headers tell the browser which cross-origin requests to allow.

Same origin: same protocol + domain + port. http://app.com:3000 and http://app.com:4000 are different origins.

Preflight Requests

For non-simple requests (PUT, DELETE, custom headers, JSON content-type), the browser sends an OPTIONS request first. If the server responds with the right CORS headers, the browser proceeds with the actual request.

OPTIONS /api/users HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
Enter fullscreen mode Exit fullscreen mode

Proper CORS Configuration

import cors from "cors";
app.use(cors({
  origin: ["https://app.example.com", "https://admin.example.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));
Enter fullscreen mode Exit fullscreen mode

Why origin: "*" Is Dangerous

Wildcard origin allows ANY website to call your API. An attacker hosts evil.com, user visits it while logged into your app, evil.com makes requests to your API with the users cookies. Use an explicit allowlist.

Also: credentials: true and origin: "*" cannot be used together. The browser rejects it.

Dynamic Origin Validation

For multi-tenant apps or dynamic subdomains:

app.use(cors({
  origin: (origin, callback) => {
    if (\!origin || /\.example\.com$/.test(new URL(origin).hostname)) {
      callback(null, true);
    } else { callback(new Error("Not allowed by CORS")); }
  }
}));
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

  1. Using origin: "*" with credentials: Browser rejects this combination
  2. CORS on the frontend: CORS is a server-side configuration. You cannot fix it from React.
  3. Forgetting OPTIONS handler: Some frameworks need explicit OPTIONS route handling for preflight
  4. No maxAge: Without caching, browsers send a preflight for every request. Set maxAge to cache the preflight response.

CORS Is Not Security

CORS only applies to browsers. curl, Postman, and server-to-server calls ignore it entirely. CORS prevents browser-based attacks like CSRF, not API abuse. Always use authentication and rate limiting regardless of CORS.


Part of my Production Backend Patterns series. Follow for more practical backend engineering.


If this was useful, consider:

Top comments (0)