CORS misconfiguration — how `Access-Control-Allow-Origin: *` breaks your app
CORS is one of the most misunderstood security headers. Here is exactly when `*` is safe, when it is catastrophic, and how to configure CORS correctly for a Next.js + Supabase stack.
CORS (Cross-Origin Resource Sharing) governs whether a browser at origin A is allowed to read a response from origin B. Misconfiguration ranges from "your API is unusable" to "any website can read your authenticated user's data".
What it is
Browsers enforce the same-origin policy by default — JavaScript on A cannot read responses from B. CORS is the opt-in mechanism where B declares which origins may read its responses. `Access-Control-Allow-Origin: *` means "anyone", `Access-Control-Allow-Origin: https://app.example.com` means "just this origin".
Vulnerable example
// Vulnerable combination:
export async function GET() {
return new Response(JSON.stringify(await getUserProfile()), {
headers: {
"Access-Control-Allow-Origin": "*", // <- anyone can read
"Access-Control-Allow-Credentials": "true", // <- with cookies!
},
});
}
// This combination is REJECTED by browsers, but the mistake pattern keeps coming up.Fixed example
// Fixed: explicit allowlist + credentials
const ALLOWED_ORIGINS = new Set([
"https://app.yourdomain.com",
"https://www.yourdomain.com",
]);
export async function GET(req: Request) {
const origin = req.headers.get("origin") ?? "";
const ok = ALLOWED_ORIGINS.has(origin);
return new Response(JSON.stringify(await getUserProfile()), {
headers: {
"Access-Control-Allow-Origin": ok ? origin : "",
"Access-Control-Allow-Credentials": ok ? "true" : "false",
"Vary": "Origin",
},
});
}How Securie catches it
apps/web/middleware.ts:45CORS misconfiguration
Securie scans every response-setter in your codebase for CORS headers. Combinations that allow credentialed access from untrusted origins are flagged critical.
// Fixed: explicit allowlist + credentials
const ALLOWED_ORIGINS = new Set([
"https://app.yourdomain.com",
"https://www.yourdomain.com",
]);
export async function GET(req: Request) {
const origin = req.headers.get("origin") ?? "";
const ok = ALLOWED_ORIGINS.has(origin);
return new Response(JSON.stringify(await getUserProfile()), {
headers: {
"Access-Control-Allow-Origin": ok ? origin : "",
"Access-Control-Allow-Credentials": ok ? "true" : "false",
"Vary": "Origin",
},
});
}Checklist
- Never combine `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`
- Maintain an allowlist of explicit origins; do not echo back the request origin without validating
- Set `Vary: Origin` when the response depends on the request origin (for CDN correctness)
- Preflight responses are short-cached — test with realistic cache settings
- Public APIs (no cookies, no auth headers) may use `*`
FAQ
Is CORS a security feature?
Partially. CORS protects users from cross-site data reads but does not protect your server from any attack. CSRF is a separate concern.
Related guides
A complete reference for the security headers your Next.js app should ship with. Configured via next.config.mjs in minutes.
BOLA is the top item on the OWASP API Security Top 10 for a reason — every AI coding assistant introduces it by default. Learn what it looks like in Next.js, how to exploit it, and how to fix it.
Every week founders tweet about their OpenAI bill going from $10 to $10,000 overnight. Usually the cause is an API key committed to a public repo. Here is why it happens in Next.js specifically and how to stop it in five minutes.
Unlimited API endpoints are how $150K OpenAI bills happen. Here is how to add proper rate limiting to a Next.js app using Vercel Edge Middleware, Upstash, or your existing Redis.