DEV Community

Siddharth
Siddharth

Posted on

🧭 Understanding CORS: Preflight vs Actual API Response

⚙️ What is a Preflight Request?

  • A preflight is an automatic OPTIONS request sent by the browser before the actual API call.
  • Purpose: to ask the server for permission to send a non-simple cross-origin request (e.g., one with custom headers like Authorization or Content-Type).

🔁 Preflight vs Actual API Response

Aspect Preflight (OPTIONS) Actual Request (GET, POST, etc.)
Who sends it Browser (automatically) Browser (after preflight passes)
Purpose Check if origin, method, and headers are allowed Perform the real API action
Typical Headers Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers Access-Control-Allow-Origin, Access-Control-Expose-Headers
Response Body Usually empty (204 No Content) Contains actual API data or error
Visible to Frontend JS No (browser internal) Yes (if CORS rules allow)

🧩 Why Preflight is Useful

  • 🛡️ Security: Prevents unauthorized cross-origin requests.
  • Negotiation: Lets the browser confirm allowed origins, methods, and headers.
  • 💬 Safety: Ensures your API only accepts intended headers and methods.

1️⃣ Middleware handles only OPTIONS

Preflight (success):

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Enter fullscreen mode Exit fullscreen mode

Actual request (fails CORS):

HTTP/1.1 200 OK
Content-Type: application/json
(no Access-Control-Allow-Origin header)
Enter fullscreen mode Exit fullscreen mode

💥 Browser blocks the response because the actual API didn’t include the same CORS header.


2️⃣ Error responses skip CORS config

Preflight (success):

Access-Control-Allow-Origin: https://app.example.com
Enter fullscreen mode Exit fullscreen mode

Actual (fails due to 500):

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
(no Access-Control-Allow-Origin header)
Enter fullscreen mode Exit fullscreen mode

💥 Browser treats this as a CORS violation — even though preflight passed.


3️⃣ Credentials + Wildcard Origin

Preflight:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Enter fullscreen mode Exit fullscreen mode

💥 Invalid combo — browsers block if Access-Control-Allow-Credentials: true is set with *.

You must return the exact origin instead:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Enter fullscreen mode Exit fullscreen mode

4️⃣ Missing Allowed Headers

Browser requests:

Access-Control-Request-Headers: Content-Type, Authorization
Enter fullscreen mode Exit fullscreen mode

Server responds (missing Authorization):

Access-Control-Allow-Headers: Content-Type
Enter fullscreen mode Exit fullscreen mode

💥 Browser rejects the request:

“Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response.”


💡 Developer Recommendations

  • Apply CORS consistently on all responses — success or error.
  • ⚙️ Use middleware per route if different APIs need different origins.
  • 🧱 Include Authorization and Content-Type in allowedHeaders if you use tokens or JSON.
  • 🔐 Be explicit — don’t rely on defaults.
  • 🧪 Test in DevTools — verify that both OPTIONS and the actual request return matching Access-Control-Allow-* headers.

🧭 TL;DR

Preflight = browser asking for permission
Actual request = performing the action

Preflight can pass while the actual request fails if headers, origins, or credentials aren’t consistent.

🔧 Fix: Apply the same Access-Control-Allow-* headers to every response — including errors — and explicitly list headers like Authorization and Content-Type.

Top comments (0)