Skip to content

/commands/cors/enable-cors-express

Enable CORS in Express with the cors middleware

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
CORS
versions
cors 2.8, Express 4 / 5
os
linux, macos, windows

Install the middleware with npm install cors and mount it before your routes: app.use(cors()) allows every origin with Access-Control-Allow-Origin: *, and app.use(cors({ origin: [...] })) allows only the listed origins, reflecting the caller's origin and adding Vary: Origin. The middleware also answers preflight OPTIONS requests with 204 No Content and the allowed methods and headers, so you do not write those handlers yourself.

Setup

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund express@5 cors@2 >/dev/null 2>&1
cat > app.js <<'EOF'
const express = require("express");
const cors = require("cors");
const app = express();

// Public endpoint: any origin, no credentials
app.get("/public", cors(), (req, res) => res.json({ ok: true }));

// API: two origins, cookies allowed, preflight cached 10 minutes
app.use("/api", cors({
  origin: ["https://app.example.com", "https://admin.example.com"],
  methods: ["GET", "POST", "PUT"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 600,
}));
app.put("/api/items", (req, res) => res.json({ saved: true }));

const server = app.listen(5209, "127.0.0.1");
setTimeout(() => server.close(() => process.exit(0)), 8000);
EOF
node app.js &
sleep 2
show() { tr -d '\r' | grep -iE '^(HTTP|access-control|vary)'; }
echo "== GET /public"
curl -s -i http://127.0.0.1:5209/public -H "Origin: https://any.example" | show
echo "== preflight from an allowed origin"
curl -s -i -X OPTIONS http://127.0.0.1:5209/api/items -H "Origin: https://admin.example.com" \
  -H "Access-Control-Request-Method: PUT" -H "Access-Control-Request-Headers: content-type" | show
echo "== preflight from another origin"
curl -s -i -X OPTIONS http://127.0.0.1:5209/api/items -H "Origin: https://evil.example" \
  -H "Access-Control-Request-Method: PUT" | show
wait

Output

== GET /public
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
== preflight from an allowed origin
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://admin.example.com
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Max-Age: 600
== preflight from another origin
HTTP/1.1 204 No Content
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT
Access-Control-Allow-Headers: Content-Type,Authorization
Access-Control-Max-Age: 600

The preflight from an origin that is not on the list still gets 204, but without Access-Control-Allow-Origin. That missing header is what makes the browser block the request with No 'Access-Control-Allow-Origin' header is present.

Options

OptionDefaultEffect
origin'*'true reflects any origin; a string, RegExp or array allows matching origins; a function (origin, cb) decides per request
methodsGET,HEAD,PUT,PATCH,POST,DELETEAccess-Control-Allow-Methods in preflight responses
allowedHeadersthe request headersAccess-Control-Allow-Headers; when unset, the middleware echoes Access-Control-Request-Headers
exposedHeadersnoneResponse headers JavaScript may read (Access-Control-Expose-Headers)
credentialsfalseAdds Access-Control-Allow-Credentials: true for cookies and HTTP auth
maxAgenoneAccess-Control-Max-Age in seconds: how long the browser caches the preflight
optionsSuccessStatus204Status of the preflight response; 200 for very old clients

Pitfalls

  • Order matters: app.use(cors(...)) must come before the routes and before anything that ends the response, such as an auth middleware that returns 401. Otherwise error responses lack the CORS headers and the browser reports a CORS error instead of the real status (see preflight does not have HTTP ok status).
  • origin: '*' plus credentials: true produces a response browsers reject; see wildcard origin with credentials.
  • Behind nginx or another proxy, make sure the proxy does not add its own Access-Control-Allow-Origin as well: two values in that header are also rejected.
  • CORS is enforced by browsers only. curl and server-to-server calls ignore it, so it is not an access control mechanism.

FAQ

Do I need app.options() for preflight requests?

Not when cors() is mounted with app.use(): the middleware answers OPTIONS preflights itself with 204 and does not call the next handler (preflightContinue is false by default). Route-level cors() on app.get() alone does not see OPTIONS, so mount it with app.use() or add an OPTIONS route.

Why is Access-Control-Allow-Origin missing for some origins?

With an origin list, the middleware adds the header only when the request's Origin is in the list. Other origins get a response without it, which the browser blocks. Check the exact spelling, scheme and port of the origin.

Can I use origin: '*' with credentials: true?

No. Browsers reject a wildcard Allow-Origin on credentialed requests. Give an explicit list or a function, so the middleware reflects the caller's origin.

Sources