Skip to content

/errors/cors/request-header-field-not-allowed-by-access-control-allow-headers

CORS: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response

The commands on this page have not been executed by our verification runner yet.updated
tool
CORS
versions
Fetch Standard, Chrome, Edge, Firefox, Safari
os
any

The browser sent a preflight (OPTIONS) request announcing, in Access-Control-Request-Headers, the headers the real request will carry. The server's answer did not include authorization (or the header named in the message) in Access-Control-Allow-Headers, so the browser did not send the real request. Add the header to Access-Control-Allow-Headers on the OPTIONS response, for example Access-Control-Allow-Headers: Content-Type, Authorization.

Reproduce it

The API allows only Content-Type; the page sends a bearer token. Microsoft Edge runs headless through playwright-core:

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund playwright-core@1 >/dev/null 2>&1
cat > preflight.js <<'EOF'
const http = require("http");
const { chromium } = require("playwright-core");
const allowHeaders = process.argv[2];
const api = http.createServer((req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5036");
  if (req.method === "OPTIONS") {
    console.log("preflight asks for:", req.headers["access-control-request-headers"]);
    res.setHeader("Access-Control-Allow-Methods", "GET, POST");
    res.setHeader("Access-Control-Allow-Headers", allowHeaders);
    res.writeHead(204); return res.end();
  }
  res.end('{"ok":true}');
}).listen(5037, "127.0.0.1");
const web = http.createServer((req, res) => res.end("<!doctype html><title>app</title>")).listen(5036, "127.0.0.1");
(async () => {
  const browser = await chromium.launch({ channel: "msedge", headless: true });
  const page = await browser.newPage();
  const errors = [];
  page.on("console", (m) => { if (m.type() === "error") errors.push(m.text()); });
  await page.goto("http://127.0.0.1:5036/");
  const result = await page.evaluate(() => fetch("http://127.0.0.1:5037/items", {
    method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer demo" }, body: "{}",
  }).then((r) => "HTTP " + r.status, (e) => e.name + ": " + e.message));
  console.log(`Allow-Headers "${allowHeaders}":`, result);
  await new Promise((r) => setTimeout(r, 300));
  for (const e of errors.filter((e) => e.includes("CORS"))) console.log(e);
  await browser.close(); api.close(); web.close();
})();
EOF
node preflight.js "Content-Type"
node preflight.js "Content-Type, Authorization"

The first run prints what the preflight asked for and the exact console message; with Authorization added to the list the preflight passes and the POST is sent. Header names are case-insensitive; the browser announces them in lower case.

Which headers need to be allowed

Only CORS-safelisted request headers skip this check: Accept, Accept-Language, Content-Language, Range (simple ranges), and Content-Type with one of application/x-www-form-urlencoded, multipart/form-data or text/plain. Everything else must be listed, including:

HeaderCommon source
AuthorizationBearer tokens, Basic auth
Content-Type: application/jsonfetch or axios with a JSON body
X-Requested-WithjQuery and some legacy libraries
X-Api-Key, X-CSRF-TokenAPI keys, CSRF protection
traceparent, sentry-trace, baggageTracing and monitoring SDKs added to every request

Server examples

js
// Express with the cors package
app.use(cors({ origin: "https://app.example.com", allowedHeaders: ["Content-Type", "Authorization"] }));
nginx
# nginx, inside the location that answers the API
if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Max-Age 600 always;
    return 204;
}

Access-Control-Allow-Headers: * allows any header for requests without credentials, but Authorization must still be named explicitly.

FAQ

Why does the request work in curl or Postman but not in the browser?

CORS is enforced only by browsers. curl and Postman send the request directly and never run a preflight, so they cannot show this error; test the OPTIONS response instead.

Does the header name have to match the case the browser uses?

No. Header names are case-insensitive, so Authorization in Access-Control-Allow-Headers covers the lower-case authorization the browser announces.

Can I avoid the preflight altogether?

Only by sending nothing but safelisted headers and methods, for example a form-encoded POST without Authorization. Serving the frontend and the API from the same origin through a reverse proxy also removes CORS from the picture.

Sources