Tidak suka iklan? Pergi Bebas Iklan Hari ini

CORS Dijelaskan Why Your API Keeps Blocking You (and How to Fix It)

Diperbarui pada

CORS errors are the bane of every frontend developer's existence. Here's what the browser is actually enforcing, why the wildcard fix fails with credentials, and how to configure CORS correctly the first time.

CORS Explained: Why Your API Keeps Blocking You (and How to Fix It) 1
IKLAN · HAPUS?

You just wrote a fetch call. The backend is running. You open DevTools and see: Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy. Congratulations — you’ve hit one of the most reliably confusing errors in web development.

Here’s the thing: CORS isn’t a bug, and it isn’t your API being hostile. It’s the browser enforcing a security policy on your behalf. Once you understand what it’s actually doing, fixing it takes about five minutes.

The Same-Origin Policy: Why This Exists

Browsers enforce the Same-Origin Policy (SOP) to stop malicious websites from silently reading data from other sites using your logged-in session. Without it, a page at evil.com could fire a request to yourbank.com/api/balance, and the browser would happily send your bank cookies along — then hand the response back to evil.com.

Two URLs share the same origin if and only if they match on all three of these:

  • Protokolhttp:// vs https:// are different origins
  • Tuan rumahapi.example.com vs example.com are different origins
  • Pelabuhanexample.com:3000 vs example.com:8080 are different origins

So when your React app on http://localhost:3000 calls an Express API on http://localhost:4000, the browser sees a cross-origin request and CORS kicks in.

Simple Requests vs. Preflighted Requests

Not all cross-origin requests are treated the same. The browser splits them into two categories:

Simple Requests

A request is “simple” if it uses GET, HEAD, atau POST with only these headers: Accept, Accept-Language, Content-Language, atau Content-Type with values application/x-www-form-urlencoded, multipart/form-data, atau text/plain.

For a simple request, the browser sends it directly and checks the Access-Control-Allow-Origin header on the response. If the origin isn’t listed, it blocks the response from JavaScript — the request still hit your server, but JS can’t read the result.

Preflighted Requests

Anything outside those constraints triggers a preflight: the browser first sends an OPTIONS request to the same URL, asking “are you okay with this?”. The preflight carries:

OPTIONS /api/data HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Content-Type, Authorization

Your server has to respond to that OPTIONS request with the appropriate CORS headers sebelum the browser will send the actual request. This catches a lot of developers off guard — more on that below.

The CORS Response Headers That Actually Matter

Here’s what each header does:

Access-Control-Allow-Origin

The most important one. Tells the browser which origins are allowed to read the response. You can set it to a specific origin or the wildcard *:

Access-Control-Allow-Origin: https://app.example.com
# or
Access-Control-Allow-Origin: *

Access-Control-Allow-Methods

Lists which HTTP methods are permitted. Used in preflight responses:

Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Headers

Which request headers the client is allowed to send. If your frontend sends Authorization or a custom header and it’s not listed here, the preflight fails:

Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header

Access-Control-Allow-Credentials

When set to true, tells the browser it’s okay to send cookies and HTTP auth headers with cross-origin requests — but hanya if the frontend also opts in with credentials: 'include':

Access-Control-Allow-Credentials: true

Access-Control-Max-Age

How long (in seconds) the browser can cache the preflight response. Without it, every single request gets a preflight. Set it to something reasonable to save round trips:

Access-Control-Max-Age: 86400

Want to quickly check what headers your API is actually returning? The Penganalisis Header HTTP lets you inspect response headers without touching curl.

The Credentials + Wildcard Trap

This is the most common “I followed the tutorial and it still doesn’t work” situation. When your request includes credentials (cookies, HTTP auth), the browser refuses to accept Access-Control-Allow-Origin: *. Full stop. The spec explicitly disallows it.

So this combination will always fail:

// Frontend
fetch('https://api.example.com/user', {
  credentials: 'include'  // sends cookies
});

// Backend response headers — THIS DOESN'T WORK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

The fix is to echo back the specific requesting origin instead of using the wildcard:

// Backend response headers — correct
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

If you have multiple valid origins, you need to check the request’s Origin header against an allowlist and reflect the matching one dynamically — not hardcode all of them (you can only return one value).

The Forgotten OPTIONS Handler

Here’s a mistake that trips up people building REST APIs: they add CORS headers to their GET dan POST route handlers, but forget that preflighted requests arrive as OPTIONS — a method their router never handles.

The browser sends OPTIONS /api/data and gets back a 404 atau 405 Method Not Allowed. Preflight fails. The actual request never happens. The error in DevTools just says CORS, so it looks like a header problem.

Your server must respond to OPTIONS requests with a 200 (atau 204) and the correct CORS headers. In Express, you can handle this globally before your routes:

app.options('*', (req, res) => {
  res.set({
    'Access-Control-Allow-Origin': req.headers.origin,
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Allow-Credentials': 'true',
    'Access-Control-Max-Age': '86400',
  });
  res.sendStatus(204);
});

A Practical Express.js CORS Middleware Example

Here’s a production-ready CORS middleware for Express that handles the dynamic origin pattern, credentials, preflight caching, and the Vary: Origin header (which tells CDNs and proxies not to serve the same cached response to different origins):

const ALLOWED_ORIGINS = new Set([
  'https://app.example.com',
  'https://staging.example.com',
  'http://localhost:3000',
]);

function corsMiddleware(req, res, next) {
  const origin = req.headers.origin;

  // Always set Vary so caches know the response depends on Origin
  res.set('Vary', 'Origin');

  if (origin && ALLOWED_ORIGINS.has(origin)) {
    res.set('Access-Control-Allow-Origin', origin);
    res.set('Access-Control-Allow-Credentials', 'true');
  }

  // Handle preflight
  if (req.method === 'OPTIONS') {
    res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
    res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.set('Access-Control-Max-Age', '86400');
    return res.sendStatus(204);
  }

  next();
}

app.use(corsMiddleware);

Itu Vary: Origin header is easy to forget and causes subtle caching bugs in production. Without it, a CDN might cache a response with Access-Control-Allow-Origin: https://app.example.com and serve it to a different origin that shouldn’t have access — or vice versa.

If you’re not sure whether your headers are set up correctly, the Pembuat & Validator Header CORS lets you construct and validate your CORS configuration before deploying anything.

Don’t Disable CORS in Your Browser

Searching for a quick fix often surfaces advice to launch Chrome with --disable-web-security. Please don’t. This disables the Same-Origin Policy entirely — not just CORS. You’re now browsing the web with no cookie isolation between sites. Any site you visit can read data from any other site you’re logged into.

The right local dev fix is a dev proxy. Vite has one built in. When the browser makes a request to http://localhost:3000/api/..., the Vite dev server proxies it to your backend — same origin as far as the browser is concerned, so CORS never triggers:

// vite.config.ts
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:4000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
};

Now your frontend calls /api/users and Vite proxies it to http://localhost:4000/users. No CORS, no browser security workarounds, and it mirrors how a reverse proxy works in production.

Quick Troubleshooting Checklist

When a CORS error hits, run through this before reaching for Stack Overflow:

  • Check the Network tab in DevTools — does the preflight OPTIONS request exist? Did it get a 2xx response?
  • Is Access-Control-Allow-Origin set in the response headers (not the request headers)?
  • Are you using credentials? If yes, * won’t work — use the exact origin.
  • Does your router handle OPTIONS for the route you’re calling?
  • Is Authorization listed in Access-Control-Allow-Headers?
  • Is Vary: Origin set if you’re dynamically reflecting origins?
Ingin bebas iklan? Bebas Iklan Hari Ini

Instal Ekstensi Kami

Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat

Ke Ekstensi Chrome Ke Ekstensi Tepi Ke Ekstensi Firefox Ke Ekstensi Opera

Papan Skor Telah Tiba!

Papan Skor adalah cara yang menyenangkan untuk melacak permainan Anda, semua data disimpan di browser Anda. Lebih banyak fitur akan segera hadir!

IKLAN · HAPUS?
IKLAN · HAPUS?
IKLAN · HAPUS?

Pojok Berita dengan Sorotan Teknologi

Terlibat

Bantu kami untuk terus menyediakan alat gratis yang berharga

Belikan aku kopi
IKLAN · HAPUS?