Implementing CORS
1. Installing cors Package
2. Enabling CORS for All Routes
Example: Default (allow any origin)
import cors from "cors";
app.use(cors()); // Access-Control-Allow-Origin: *
Warning: cors() with no options allows any origin and forbids credentials. Lock down with origin: in production.
3. Configuring Allowed Origins
Example: Allowlist
const allowed = new Set(["https://app.example.com","https://admin.example.com"]);
app.use(cors({
origin: (origin, cb) => {
if (!origin || allowed.has(origin)) return cb(null, true);
return cb(new Error("CORS: Origin not allowed"));
}
}));
4. Setting Allowed Methods
Example: methods
app.use(cors({
methods: ["GET","POST","PUT","PATCH","DELETE"],
allowedHeaders: ["Content-Type","Authorization","Idempotency-Key"]
}));
5. Allowing Credentials
Example: Credentials
app.use(cors({
origin: "https://app.example.com", // MUST be exact, NOT "*"
credentials: true
}));
| credentials | Required for |
| true | Cookies, Authorization header, TLS client certs |
| false | Public APIs without per-user state |
app.use(cors({
exposedHeaders: ["X-Total-Count","Link","X-Request-Id"]
}));
7. Setting Preflight Max Age
Example: maxAge
app.use(cors({ maxAge: 86400 })); // cache preflight for 24h
8. Enabling CORS for Specific Routes
Example: Per-route
app.get("/public/data", cors(), (req, res) => res.json({ ok: true }));
app.options("/public/data", cors()); // preflight
9. Handling Dynamic Origins
Example: Subdomain regex
const ORIGIN_RE = /^https:\/\/([a-z0-9-]+\.)?example\.com$/;
app.use(cors({
origin: (origin, cb) => (!origin || ORIGIN_RE.test(origin)) ? cb(null, true) : cb(new Error("Not allowed")),
credentials: true
}));
10. Pre-flight Request Handling
Browser Server
│ │
│── OPTIONS /api/users ────────────────▶│
│ Origin: https://app.example.com │
│ Access-Control-Request-Method: PUT │
│ Access-Control-Request-Headers: ... │
│ │
│◀── 204 No Content ────────────────────│
│ Access-Control-Allow-Origin: ... │
│ Access-Control-Allow-Methods: ... │
│ Access-Control-Max-Age: 86400 │
│ │
│── PUT /api/users ────────────────────▶│
| Trigger | Preflight required? |
| Simple GET/HEAD/POST with simple headers | No |
| PUT/PATCH/DELETE | Yes |
| Custom headers (e.g., Authorization) | Yes |
| Content-Type other than form-urlencoded/text/multipart | Yes |