CORS Explained in 2026: Fixing Cross‑Origin Errors Fast
March 13, 2026 · Security & Auth, Web APIs, DevOps
CORS (Cross-Origin Resource Sharing) is a browser security policy that blocks a web page from making requests to a different origin unless the server explicitly allows it. If you’ve seen errors like “No ‘Access-Control-Allow-Origin’ header is present” in your console, you’ve hit CORS.
This guide explains exactly why CORS happens, how preflight requests work, and how to fix cross-origin errors in production and local development. It’s designed to be practical and bookmark‑worthy, with concrete fixes for common stacks.
What “origin” actually means
An origin is the combination of scheme, host, and port. These three values must all match for two URLs to be same‑origin.
- https://app.example.com and https://api.example.com are different origins (host differs).
- https://app.example.com and http://app.example.com are different origins (scheme differs).
- https://app.example.com and https://app.example.com:8443 are different origins (port differs).
CORS only applies to requests made by browsers (like fetch, XHR, or browser‑based GraphQL clients). Server‑to‑server requests are not blocked by CORS.
Why browsers enforce CORS
Browsers enforce CORS to prevent malicious sites from reading data from other origins using a user’s existing authenticated session. CORS is not about blocking the request itself; it’s about controlling whether the response is readable by the browser.
That’s why you can often see the network request go through, yet JavaScript still fails to read the response with a CORS error.
Typical CORS error messages (and what they mean)
- No ‘Access-Control-Allow-Origin’ header is present — The server didn’t allow your origin.
- CORS policy: Response to preflight request doesn’t pass access control check — The server didn’t allow the requested method/headers in the OPTIONS preflight.
- Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’ — You used cookies or Authorization with wildcard origin.
Simple requests vs. preflighted requests
CORS behaves differently depending on request type:
Simple requests (no preflight)
These are allowed if the response includes the right CORS headers. A request is “simple” if it uses:
- Method: GET, HEAD, or POST
- Headers: only
Accept,Accept-Language,Content-Language,Content-Type(with limited values) - Content-Type:
application/x-www-form-urlencoded,multipart/form-data, ortext/plain
Preflighted requests (OPTIONS)
If you use JSON, custom headers, or methods like PUT/DELETE, the browser sends an OPTIONS preflight to ask the server for permission first.
CORS response headers you must understand
- Access-Control-Allow-Origin — Which origins are allowed to read the response.
- Access-Control-Allow-Methods — Which HTTP methods are allowed.
- Access-Control-Allow-Headers — Which headers the browser can send.
- Access-Control-Allow-Credentials — Whether cookies/Authorization are allowed.
- Vary: Origin — Prevents cache poisoning when you dynamically set origins.
Fixing CORS on the server (real examples)
The correct fix is almost always on the server. Here are practical examples.
Node.js (Express)
import express from "express";
import cors from "cors";
const app = express();
app.use(cors({
origin: ["https://app.example.com", "https://admin.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
maxAge: 600
}));
app.get("/api/profile", (req, res) => {
res.json({ id: 1, name: "Ada" });
});
app.listen(8080);
Tip: If you allow credentials, you cannot use * for Access-Control-Allow-Origin. Use a specific origin list.
Python (FastAPI)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
max_age=600
)
@app.get("/api/profile")
def profile():
return {"id": 1, "name": "Ada"}
Go (net/http)
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if origin == "https://app.example.com" {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Vary", "Origin")
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
Common mistakes that cause CORS failures
- Using credentials with a wildcard origin — Browsers reject it. Use explicit origins.
- Not responding to OPTIONS — Preflight fails and the real request never runs.
- Allowing wrong headers — If the browser requests
Authorizationbut you only allowContent-Type, it fails. - Mixing HTTP/HTTPS — A secure page (HTTPS) calling HTTP is blocked by mixed content, not CORS.
Debug CORS quickly (repeatable checklist)
- Check the Origin header in the request.
- For preflight, inspect the OPTIONS response headers.
- Verify
Access-Control-Allow-Originmatches exactly. - Confirm
Access-Control-Allow-Headersincludes every custom header you send. - Ensure your server returns
Vary: Originif origin is dynamic.
When testing payloads, the JSON Formatter makes it easy to validate responses and ensure your API is returning valid JSON before you chase CORS ghosts.
Local development fixes
Local development is where CORS pain peaks because your frontend and backend run on different ports.
Option A: Use a local proxy (best dev experience)
Webpack, Vite, Next.js, and CRA all support a dev proxy. This keeps the browser on one origin.
// vite.config.js
export default {
server: {
proxy: {
"/api": "http://localhost:8080"
}
}
}
Option B: Enable CORS in dev only
Allow localhost origins like:
allow_origins=["http://localhost:3000", "http://127.0.0.1:5173"]
Option C: Use a reverse proxy (Nginx)
server {
listen 80;
server_name app.local;
location /api/ {
proxy_pass http://localhost:8080/;
}
}
Handling JSON and custom headers safely
When you send JSON or auth tokens, you trigger preflight. That’s normal—just allow it explicitly.
fetch("https://api.example.com/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
credentials: "include",
body: JSON.stringify({ plan: "pro" })
});
If this fails, check Access-Control-Allow-Headers includes Authorization and Content-Type, and that the server responds to OPTIONS with a 204 or 200.
CORS vs. CSRF vs. auth headers
CORS doesn’t replace authentication. It’s a browser read‑protection mechanism. You still need proper auth (JWT, session cookies, OAuth) and CSRF protections when using cookies.
A helpful mental model:
- CORS decides if the browser can read the response.
- Auth decides if the server allows the request.
- CSRF protects cookie-based sessions from cross-site misuse.
Hardening CORS for production
- Allow only known origins (no wildcard).
- Set
maxAge(e.g., 600 seconds) to reduce preflight spam. - Return
Vary: Originwhen dynamically setting origin. - Log preflight failures to catch misconfigurations early.
Practical debugging tools
Here are a few DevToolKit helpers that make CORS troubleshooting faster:
- URL Encoder/Decoder — verify query strings and encoded origins.
- Base64 Encoder/Decoder — inspect Basic auth headers or token payloads.
- JSON Formatter — validate API responses quickly.
Example: Full CORS setup with credentials
This is a common production pattern for a React frontend and API on separate subdomains.
// Express + CORS with credentials
app.use(cors({
origin: "https://app.example.com",
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
maxAge: 600
}));
Browser request:
fetch("https://api.example.com/me", {
credentials: "include"
});
Required response headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Key takeaways
- CORS is a browser policy, not a server firewall.
- Fixes are applied on the server by returning correct headers.
- Preflight is normal when using JSON or Authorization headers.
- Wildcard origins and credentials don’t mix.
- Use a dev proxy to eliminate most local CORS pain.
FAQ
Need to validate or debug your API output quickly? Use the JSON Formatter to verify response structure before chasing CORS errors.
Recommended Tools & Resources
Level up your workflow with these developer tools:
Auth0 → Cloudflare Zero Trust → Web Application Security by Andrew Hoffman →Dev Tools Digest
Get weekly developer tools, tips, and tutorials. Join our developer newsletter.