⚙️ What is a Preflight Request?
- A preflight is an automatic
OPTIONSrequest 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
AuthorizationorContent-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
Actual request (fails CORS):
HTTP/1.1 200 OK
Content-Type: application/json
(no Access-Control-Allow-Origin header)
💥 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
Actual (fails due to 500):
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
(no Access-Control-Allow-Origin header)
💥 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
💥 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
4️⃣ Missing Allowed Headers
Browser requests:
Access-Control-Request-Headers: Content-Type, Authorization
Server responds (missing Authorization):
Access-Control-Allow-Headers: Content-Type
💥 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
AuthorizationandContent-TypeinallowedHeadersif you use tokens or JSON. - 🔐 Be explicit — don’t rely on defaults.
- 🧪 Test in DevTools — verify that both
OPTIONSand the actual request return matchingAccess-Control-Allow-*headers.
🧭 TL;DR
Preflight = browser asking for permission
Actual request = performing the actionPreflight 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 likeAuthorizationandContent-Type.
Top comments (0)