CORS Handbook
The complete guide to understanding and fixing CORS errors.
Built by headertest.com
CORS on Azure Functions looks simple until you ship something with auth, multiple environments, and a frontend team that keeps changing origins every sprint. I’ve seen teams treat CORS as a checkbox in the Azure Portal, then spend hours debugging why Authorization headers fail, why local dev works but production doesn’t, or why preflight requests get blocked before their function code even runs. If you’re building browser-facing APIs on Azure Functions, you have a few ways to handle CORS. Some are easy. Some are flexible. Some are traps. ...
CORS in Deno and Bun feels similar at first because both runtimes lean hard into the Web Platform. You get Request, Response, Headers, and fetch, so the mechanics are familiar. The difference shows up when you actually wire policies into a real server, especially once preflight requests, credentials, and route-level behavior enter the picture. My short take: Deno feels more explicit and standards-first. Bun feels faster to get running and very ergonomic, but you need to be just as disciplined about the policy because neither runtime magically saves you from bad CORS decisions. ...
CORS and CSRF get lumped together way too often. I’ve seen teams say “we enabled CORS, so we’re protected from CSRF now” and then act surprised when their app still has a cross-site request forgery bug. That’s the core mistake: these are not competing solutions to the same problem. They deal with different threats, at different layers, using different browser behavior. If you only remember one thing, make it this: ...
If you build a Rails API and your frontend runs on a different origin, CORS stops being theory pretty fast. You ship an endpoint, the browser blocks it, and suddenly everyone is staring at a console error that says “No ‘Access-Control-Allow-Origin’ header.” Rails itself does not magically solve CORS. You need to configure it intentionally, and if you get lazy with wildcards or credentials, you can open up more access than you meant to. ...
GraphQL subscriptions are where CORS advice usually gets sloppy. A lot of teams learn CORS from regular fetch() requests, then bolt on subscriptions and assume the same rules apply. They do not. The transport matters: HTTP GraphQL queries/mutations: normal CORS rules WebSocket subscriptions: not governed by browser CORS in the same way SSE subscriptions: back to normal HTTP-origin behavior Multipart/deferred streaming over HTTP: also normal CORS behavior If you only remember one thing, remember this: GraphQL subscriptions over WebSocket do not use CORS the way fetch() does, but origin validation still matters. ...
COOP and CORS get mixed together all the time, and I get why. They both have “cross-origin” in the name, both involve headers, and both can break your app in ways that feel random. But they solve different problems. CORS controls whether a page can read a cross-origin HTTP response in JavaScript. COOP, via the Cross-Origin-Opener-Policy header, controls whether a top-level document stays in the same browsing context group as pages it opens or pages that open it. That affects window.opener, popup relationships, process isolation, and features like SharedArrayBuffer when combined with other headers. ...
CORS gets weird fast once a CDN sits in front of your app. Without a CDN, you mostly think about browser rules: Origin, preflights, Access-Control-Allow-Origin, maybe credentials. Add a CDN and now you also have cache keys, header normalization, OPTIONS caching, stale variants, and the classic bug where one origin gets cached and leaked to another. I’ve seen teams debug this for hours because the app server was “correct” but the CDN was serving the wrong cached CORS headers. ...
CORS gets messy fast in microservices. A single frontend might call an API gateway, which fans out to auth, billing, search, notifications, and a couple of legacy services nobody wants to touch. Then one team enables Access-Control-Allow-Origin: *, another requires cookies, a third forgets OPTIONS, and suddenly the browser is your loudest incident reporter. This guide is the version I wish more teams used: practical rules, copy-paste configs, and the stuff that breaks in real systems. ...
CORS gets weird the moment DNS enters the room. A lot of teams assume that if they point api.example.com to a vendor with a CNAME, the browser will somehow treat that endpoint as “same-origin enough” with app.example.com. It won’t. Browsers care about origin, not your DNS intent. A custom subdomain can make things cleaner for cookies, branding, and certificate management, but it does not magically bypass CORS. That distinction matters when you’re choosing between: ...
If you’ve worked with fetch() long enough, CORS feels familiar: preflights, Access-Control-Allow-Origin, blocked responses, weird credentials rules. Then you open a WebSocket from a browser and things get weird fast. You expect CORS to kick in. Usually it doesn’t. That surprises a lot of people because WebSockets start as an HTTP request. But the browser does not apply the normal CORS enforcement model to a WebSocket upgrade the same way it does for fetch() or XHR. Instead, browsers send an Origin header during the handshake, and the server is expected to decide whether to accept the connection. ...