CORS wide open
Reflecting any website's Origin and allowing credentials means that website can call your API as the logged-in user.
Last updated
Shows up in: Next.js, Supabase, Firebase, Vercel
What it is
CORS is the browser's rule for whether JavaScript on site A may read a response from site B. It is not a firewall. A non-browser client ignores it. Your server still needs authentication.
The dangerous configuration is specific: your API sets Access-Control-Allow-Origin to whatever Origin header just arrived, and also sets Access-Control-Allow-Credentials: true. Then https://evil.example can run fetch("https://your-app.example/api/notes", { credentials: "include" }) and read the JSON using the victim's cookies, if the victim is logged in and visits the attacker's page.
Access-Control-Allow-Origin: * cannot be combined with credentials. Browsers reject that pair. The bug people actually ship is the reflected origin, often copied from a snippet that "fixes CORS" by echoing the request.
Supabase's Data API is called from browsers on purpose. The publishable key is public, and row level security is the control. Locking down Supabase's CORS is not how you protect tables. Protect your own cookie-authenticated routes.
How to check in 5 minutes
Search for
Access-Control-Allow-Originandcors. Note any*and any code that copiesrequest.headers.get("origin")into the response.If you also see
Allow-Credentials: trueorcredentials: truein the CORS options, the allowlist must be your real origins only.With a throwaway page on another origin, or with curl, send
Origin: https://example.comto your API. If the response reflectshttps://example.comand allows credentials, the check failed.Confirm production and preview URLs you actually use are the ones on the list, including the Vercel preview host only if previews should be allowed to call production. Usually they should not.
How to fix
Next.js
const allowed = new Set([
"https://your-app.example",
]);
export function cors(request: Request): Headers {
const headers = new Headers();
const origin = request.headers.get("origin");
if (origin && allowed.has(origin)) {
headers.set("Access-Control-Allow-Origin", origin);
headers.set("Vary", "Origin");
headers.set("Access-Control-Allow-Credentials", "true");
}
return headers;
}
If the route does not use cookies, you often do not need CORS credentials at all. Same-origin fetches from your own Next.js pages do not need a CORS middleware. Delete the "allow all" helper if nothing cross-origin is supposed to call you.
Supabase
Keep using RLS. Do not put the secret key in the browser to "avoid CORS." If you added custom headers on a Next.js proxy in front of Supabase, apply the allowlist to that proxy, not a wildcard.
Firebase
The Firebase client SDK is meant to run in the browser. Rules decide access. A wildcard on your own Cloud Function or Route Handler is a separate choice. Allowlist origins there the same way.
Vercel
You can set headers in vercel.json or next.config.ts. A static * header on /api/:path* is the bug if those routes use cookies. Prefer the allowlist in code so Vary: Origin is correct.
Copy-paste prompt
Paste this into Cursor, Claude Code, or whichever agent is editing the repo. Read the diff before you merge it.
You are closing an open CORS policy. Do not add features. Find every Access-Control-Allow-Origin header and CORS middleware. 1. If a route uses cookies or Authorization from the browser, allow only origins I list. Do not reflect the request Origin by default. 2. Do not send Allow-Credentials together with *. 3. Add Vary: Origin when the allow-origin value depends on the request. 4. Remove CORS headers from routes that are only called same-origin, instead of opening them. 5. Do not change Supabase RLS. CORS is not the database control. If you do not know the production origin, use a clearly named allowlist constant and tell me to fill it in. Do not default it to *. Show the before and after.
Questions
Does CORS protect my API from curl?
No. CORS is enforced by browsers. Authentication and RLS protect the data. CORS stops other websites from riding a logged-in browser session.
Our mobile app has no origin. Did we break it?
Native apps are not subject to browser CORS. An allowlist for websites does not block a mobile client that sends no Origin, unless your server rejects missing origins on purpose. Do not open the allowlist to * to make the mobile app work.