Suggest an editImprove this articleRefine the answer for “What is CORS and how to configure it in Express.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**CORS** (Cross-Origin Resource Sharing) is a browser policy that blocks requests to origins different from the page's own unless the server sends the right `Access-Control-*` headers. In Express.js, the `cors` package is the standard way to configure this. ```js app.use(cors({ origin: 'https://myapp.com', allowedHeaders: ['Content-Type', 'Authorization'], credentials: true })); ``` **Key rule:** `origin: '*'` combined with `credentials: true` is rejected by browsers. Use an exact origin when cookies or auth headers are involved.Shown above the full answer for quick recall.Answer (EN)Image**CORS (Cross-Origin Resource Sharing)** is a browser security policy that blocks JavaScript from making HTTP requests to a different origin unless the server explicitly permits it via response headers. ## Theory ### TL;DR - CORS is enforced by the browser, not the server. Postman and curl ignore it entirely. - A different origin means any mismatch in protocol, domain, or port: `http` vs `https`, `myapp.com` vs `api.myapp.com`, `:3000` vs `:4000`. - Before complex requests, the browser sends an automatic OPTIONS preflight to check what the server allows. - `origin: '*'` with `credentials: true` is rejected by browsers. Use one exact origin when cookies are involved. - In production: set `origin` to your frontend URL, `credentials: true` for cookies, and list `Authorization` in `allowedHeaders`. ### Quick example ```js const express = require('express'); const cors = require('cors'); const app = express(); // Dev: allow all origins app.use(cors()); // Production: restrict to your frontend app.use(cors({ origin: 'https://myapp.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, // required for cookies and auth headers maxAge: 86400 // cache preflight response for 24h })); app.get('/api/data', (req, res) => res.json({ ok: true })); app.listen(3000); ``` `cors()` with no options sets `Access-Control-Allow-Origin: *` and handles OPTIONS preflights automatically. With options, only the listed origin gets the header back. ### What counts as a different origin Origin is protocol + domain + port. All three must match. `https://myapp.com` and `http://myapp.com` are different origins. So are `myapp.com:3000` and `myapp.com:4000`. Even `localhost:3000` and `127.0.0.1:3000` are separate origins in the browser's view, which catches a lot of developers off guard during local development. ### How preflight works When JavaScript sends a non-simple request (anything with an `Authorization` header, or methods like PUT and DELETE), the browser does not fire the actual request first. It sends a preflight: an automatic OPTIONS request listing the intended method and headers. ``` Browser → OPTIONS /api/login Access-Control-Request-Method: POST Access-Control-Request-Headers: Authorization Server → 200 OK Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Methods: POST Access-Control-Allow-Headers: Authorization ``` If those response headers are missing or wrong, the real request never fires. The `cors` package handles preflights automatically. Writing manual middleware means you need an explicit `req.method === 'OPTIONS'` check. ### When to use what - Local dev, quick iteration: `app.use(cors())` with no options. - Single frontend: `origin: 'https://yourapp.com'`. - Multiple frontends: pass an array or a validation callback. - Cookies or auth tokens: `credentials: true` with an exact origin, not `'*'`. - Public API with no auth: `origin: true` (reflects each request origin back), skip credentials. ### Common mistakes **`origin: '*'` combined with `credentials: true`** Browsers block this combination. The spec forbids wildcard origins when credentials are involved. ```js // Wrong - browser throws a CORS error at runtime cors({ origin: '*', credentials: true }); // Correct cors({ origin: 'https://myapp.com', credentials: true }); ``` **`app.use(cors())` placed after routes** Express runs middleware in order. Routes registered before `cors()` respond without the CORS headers set. ```js // Wrong app.get('/api/data', handler); app.use(cors()); // Correct app.use(cors()); app.get('/api/data', handler); ``` **Missing `allowedHeaders` when sending `Authorization`** The `Authorization` header is not simple. It triggers a preflight, and the server must list it in `Access-Control-Allow-Headers`. Without it, the preflight fails and the real request never goes through. This is the most searched CORS issue on Stack Overflow. ```js // Preflight fails - Authorization not declared cors({ origin: 'https://myapp.com' }); // Correct cors({ origin: 'https://myapp.com', allowedHeaders: ['Content-Type', 'Authorization'] }); ``` **`localhost` vs `127.0.0.1` in development** They resolve to the same machine but the browser treats them as separate origins. Allow both explicitly. ```js origin: ['http://localhost:3000', 'http://127.0.0.1:3000'] ``` ### Real-world usage - **Create React App**: `proxy` in `package.json` handles CORS in dev; the Express config covers production. - **Next.js**: `res.setHeader('Access-Control-Allow-Origin', ...)` in API routes, or a shared `corsOptions` object across handlers. - **NestJS**: `app.enableCors({ origin: process.env.ALLOWED_ORIGINS })` in `main.ts`. - **Strapi**: `config/middlewares.js` with the cors plugin config. ### Follow-up questions **Q:** What is a preflight request? **A:** An automatic OPTIONS request the browser sends before non-simple requests. It checks whether the server allows the method and headers before the actual request fires. **Q:** Why does Postman work but the browser fails? **A:** Postman does not enforce the Same-Origin Policy. It sends requests directly, without preflight checks. CORS is a browser concept only. **Q:** How do you debug CORS in Chrome? **A:** Open DevTools, go to Network, look for a failed OPTIONS request (status 0 or 403). The Console will show "No 'Access-Control-Allow-Origin' header". Clear cache while testing to avoid stale preflight responses. **Q:** Can `credentials: true` work with multiple origins? **A:** Not with a static string. Browsers require one exact origin in the response header when credentials are sent. Use a callback that validates the incoming origin and returns it: `origin: (o, cb) => cb(null, allowed.includes(o) ? o : false)`. **Q:** What is an opaque response and when does it appear? **A:** When you fetch with `mode: 'no-cors'`, the browser returns an opaque response: no visible body, no status, no headers. Works for loading images or scripts from external CDNs, but useless for JSON APIs. Service Workers can cache opaque responses with some size calculation restrictions. ## Examples ### Basic dev setup ```js const express = require('express'); const cors = require('cors'); const app = express(); app.use(cors()); // sets Access-Control-Allow-Origin: * on all routes app.get('/api/status', (req, res) => { res.json({ status: 'ok' }); }); app.listen(3000); ``` `cors()` with no arguments is fine for a local API you are testing. Replace it with explicit options before deploying anywhere. ### Production config with cookie-based auth ```js const express = require('express'); const cors = require('cors'); const app = express(); const corsOptions = { origin: process.env.FRONTEND_URL || 'https://myapp.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, // lets browser send cookies and auth headers maxAge: 86400 // browser caches the preflight for 24h, fewer OPTIONS roundtrips }; app.use(cors(corsOptions)); app.use(express.json()); app.post('/api/login', (req, res) => { res.cookie('token', 'jwt-here', { httpOnly: true, secure: true }); res.json({ user: 'logged-in' }); }); app.listen(3000); ``` On the React side: `fetch('/api/login', { method: 'POST', credentials: 'include' })`. Both sides need to opt in: `credentials: true` on the server and `credentials: 'include'` on the client. Miss one, cookies do not travel. ### Dynamic multi-origin validation ```js const allowedOrigins = [ 'https://myapp.com', 'https://admin.myapp.com', 'http://localhost:3000', 'http://127.0.0.1:3000', // separate origin in the browser's view ]; app.use(cors({ origin: (origin, callback) => { // allow server-to-server requests and Postman (no Origin header) if (!origin) return callback(null, true); if (allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error(`Origin ${origin} not allowed`)); } }, credentials: true, })); ``` The callback returns one exact origin per request, which is what browsers require when `credentials: true`. I have seen projects skip the `!origin` guard and then wonder why their CI health checks break. Those requests arrive with no `Origin` header and get blocked by the callback.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.