CORS errors are one of the most common sources of confusion for web developers. Firefox's DevTools provide detailed CORS information. Here's how to understand and fix CORS issues using Firefox.
What CORS Is (Briefly)
The Same-Origin Policy prevents scripts on site-a.com from making requests to site-b.com and reading the responses. This is a security boundary.
CORS (Cross-Origin Resource Sharing) is the mechanism for servers to explicitly allow cross-origin requests by adding specific response headers.
When your JavaScript makes a fetch/XHR request to a different origin:
- Browser sends a preflight
OPTIONSrequest (for most non-simple requests) - Server responds with headers indicating what's allowed
- If allowed, browser makes the actual request
- If not allowed, browser blocks the response (CORS error)
The server controls what's allowed. The browser enforces it.
Reading CORS Errors in Firefox
Console error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource
at https://api.example.com/data. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Firefox's error messages are specific about the reason. Common reasons:
-
CORS header 'Access-Control-Allow-Origin' missing— server didn't set the header -
CORS header 'Access-Control-Allow-Origin' does not match 'https://yoursite.com'— header present but wrong value -
CORS request did not succeed— network error, server didn't respond at all -
CORS preflight response did not succeed— the OPTIONS preflight failed
Network panel: The blocked request appears as red. Click it → Headers tab → see the actual request/response headers to understand what's missing.
For a preflight request, look for the OPTIONS request in the Network panel — that's your preflight. Check if it got a 200 response with the right CORS headers.
The Required CORS Headers
Minimum for a simple GET request from any origin:
Access-Control-Allow-Origin: https://yourfrontend.com
Or to allow all origins (only for public APIs):
Access-Control-Allow-Origin: *
For POST/PUT/DELETE with JSON body (preflight required):
Access-Control-Allow-Origin: https://yourfrontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
For requests with credentials (cookies, auth headers):
Access-Control-Allow-Origin: https://yourfrontend.com # Must be specific, not *
Access-Control-Allow-Credentials: true
With credentials, Access-Control-Allow-Origin: * doesn't work — you must specify the exact origin.
The Preflight: When It Triggers
A preflight OPTIONS request is sent when:
- Method is not GET, HEAD, or POST
- Content-Type is
application/json(or anything other than form content types) - Custom headers are present (e.g., Authorization)
-
withCredentials: trueis set
For a simple GET with no custom headers, there's no preflight. For any API call with Content-Type: application/json, there is.
The preflight response must include:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourfrontend.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400 # Cache preflight for 1 day
If the preflight fails (wrong headers, wrong status code), the actual request is never sent.
Fixing CORS on the Server
Express.js (Node):
const cors = require('cors');
app.use(cors({
origin: 'https://yourfrontend.com', // Or ['https://a.com', 'https://b.com']
credentials: true, // If you need cookies/auth headers
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
Nginx:
add_header 'Access-Control-Allow-Origin' 'https://yourfrontend.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
Django Rest Framework:
# settings.py
INSTALLED_APPS = [..., 'corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOWED_ORIGINS = ['https://yourfrontend.com']
Debugging in Firefox DevTools
- Open Network panel before making the request
- Reproduce the CORS error
- Find the failed request (red row)
- Click it → Response Headers: look for
Access-Control-Allow-Origin- Missing? Server needs to add CORS headers
- Present but wrong value? Origin mismatch — fix the allowed origin list
- If you see an OPTIONS preflight: look at its response — does it have the CORS headers? HTTP 200 or 204?
Common Mistakes
Using * with credentials: You cannot use Access-Control-Allow-Origin: * when credentials: true. Must specify the exact origin.
Forgetting OPTIONS handling: Your server must respond to OPTIONS preflight requests with CORS headers. Many server frameworks ignore OPTIONS by default.
Case sensitivity: Header names like Authorization must match exactly what your request sends.
Wildcard for specific headers: Access-Control-Allow-Headers: * works, but doesn't allow Authorization header in all browsers. Explicitly list the headers you allow.
Testing locally: http://localhost:3000 and http://localhost:3001 are different origins. CORS applies even locally.
A Development Workaround (Proxy)
For development, avoid CORS issues entirely by proxying API requests through your dev server:
Create React App / Vite:
// package.json or vite.config.js
{
"proxy": "https://api.production.com"
}
Your dev server proxies requests from localhost:3000/api → api.production.com/api. Browser sees same origin (localhost). No CORS.
In production, your deployment infrastructure handles routing. This is how most SPA + API setups work.
Firefox vs Chrome CORS Differences
Firefox and Chrome enforce CORS identically (per the spec). The main differences are in error messaging:
- Firefox's console errors are often more specific about which header is wrong
- Firefox's Network panel shows the blocked request details clearly
Both browsers block CORS violations at the same points in the spec. If you hit a CORS error in one, you'll hit it in both.
Top comments (0)