CORS Handbook
The complete guide to understanding and fixing CORS errors.
Built by headertest.com
If you build APIs with FastAPI, you’re going to touch CORS almost immediately. Usually right after your frontend starts throwing blocked by CORS policy in DevTools and everyone suddenly becomes a browser networking expert. FastAPI makes CORS easy enough to turn on, but the hard part is choosing the right setup. There’s a big difference between “make the error go away” and “configure cross-origin access without creating a mess.” Here’s the practical comparison guide I wish more teams used. ...
Mobile developers get told weird things about CORS. I’ve heard all of these: “Mobile apps don’t use CORS.” “Just set Access-Control-Allow-Origin: * and move on.” “CORS is only a frontend problem.” “If the API is private, CORS doesn’t matter.” Some of that is half true, which is usually worse than being completely wrong. If you’re building a backend for iOS or Android, you need to understand when CORS applies, when it doesn’t, and why your support queue suddenly fills up the moment someone adds a webview, an admin dashboard, or a docs playground running in the browser. ...
CORS gets weird at the edge. On a normal app server, you usually control one thing: the response. At the edge, you control the response, the cache key, sometimes the request headers, and sometimes a chain of proxies you barely remember setting up six months ago. That’s where small CORS mistakes turn into “works in curl, fails in browser” bugs. This guide is the version I wish I had the first few times I debugged CORS on a CDN or edge worker. ...
If you build a frontend that calls Firebase Functions from a different origin, CORS stops being “that browser thing” and turns into a real deployment concern fast. Firebase gives you a few ways to deal with CORS, and they’re not equal. Some are clean and low-friction. Some are easy to misuse. Some look secure until you add credentials and realize you just shipped a broken policy. Here’s the practical comparison guide I wish more people had before copy-pasting Access-Control-Allow-Origin: * into every function. ...
A lot of CORS bugs are really OAuth2 architecture bugs wearing a fake mustache. I’ve seen teams spend days tweaking Access-Control-Allow-Origin headers when the real problem was simpler: they were trying to run the wrong OAuth2 flow in the browser, or they expected the browser to carry cookies and tokens across origins in ways it never will. Here’s a case study based on a very normal setup: frontend: https://app.example.com API: https://api.example.com auth server: https://auth.example.com The team had a React SPA talking directly to the API. They wanted users to click “Login with OAuth”, get redirected to the auth server, come back with a session, and then call the API with fetch(). ...
Setting CORS at the web server level is often the cleanest approach. Your application doesn’t need to know about CORS at all — Nginx or Apache handles it before the request even reaches your app. Here are configurations I’ve used in production that work. Nginx Basic: Single Origin server { listen 80; server_name api.example.com; location / { add_header Access-Control-Allow-Origin "https://myapp.com"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type, Authorization"; add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Max-Age "86400"; add_header Access-Control-Expose-Headers "X-Total-Count, X-Request-Id"; # Handle preflight if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "https://myapp.com"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type, Authorization"; add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Max-Age "86400"; return 204; } proxy_pass http://127.0.0.1:3000; } } Note: You need to repeat the add_header directives inside the if block because Nginx’s if directive creates a new context. Headers set outside the if don’t apply inside it. This is a well-known Nginx gotcha. ...
Every time your React app sends a JSON POST request, the browser does something you might not expect: it sends TWO requests instead of one. The first is an OPTIONS “preflight” request. The second is your actual request. This confuses a lot of people. Why is the browser sending extra requests? Why is my API getting OPTIONS requests I never wrote endpoints for? Why does Postman work but the browser doesn’t? ...
I’ve heard developers say “I need to add CORS to my API for security” more times than I can count. That’s backwards. CORS doesn’t make your API more secure. In fact, it makes it less restricted. The security feature is the Same-Origin Policy. CORS is the controlled exception. Let me clear this up once and for all. Same-Origin Policy (SOP) The Same-Origin Policy is a built-in browser security mechanism. It’s been around since the early days of the web. Here’s what it does: ...
This is the page I keep coming back to when I need to remember the exact syntax or behavior of a CORS header. I’m putting it all in one place so you don’t have to hunt through MDN and Stack Overflow. Response Headers (What Your Server Sends) These are the headers your API server needs to send. The browser reads these to decide whether to allow the cross-origin request. Access-Control-Allow-Origin The single most important CORS header. Without it, nothing works. ...
Express makes CORS relatively painless, but there are a few gotchas that catch people off guard. Let me walk through every setup I’ve seen work in production. The cors Package (Easiest Option) npm install cors The One-Liner (Development Only) const cors = require('cors'); app.use(cors()); ```text This allows all origins, all methods, all headers. Fine for local development. Do NOT use this in production. ### Allow a Single Origin ```javascript const cors = require('cors'); app.use(cors({ origin: 'https://myapp.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 86400, })); Allow Multiple Origins This is where it gets slightly tricky. The cors package doesn’t accept an array for origin — it accepts a function: ...