| name | cors-configuration |
| description | Configure CORS correctly to prevent credential leaks and unauthorized cross-origin access. Covers Express, Next.js, Cloudflare Workers, Laravel, and FastAPI. Use when setting up CORS, fixing preflight errors, or auditing cross-origin policies. |
| version | 1.0 |
CORS Configuration
Configure Cross-Origin Resource Sharing correctly. Misconfigured CORS is one of the most common vibe-coder mistakes -- wildcard origins with credentials enabled is equivalent to having no security at all.
The Rule
NEVER use Access-Control-Allow-Origin: * with credentials. ALWAYS whitelist specific origins.
Common Mistakes
| Mistake | Risk | Fix |
|---|
Origin: * with credentials | Any site can steal user data | Whitelist specific origins |
Reflecting Origin header without validation | Attacker controls allowed origin | Check against allowlist |
Allowing null origin | Sandboxed iframes can exploit | Never allow null |
Overly broad regex (*.example.com) | evil-example.com matches | Use exact string matching |
| Exposing all headers | Information leakage | Only expose needed headers |
Framework Implementations
Cloudflare Workers
const ALLOWED_ORIGINS = [
'https://myapp.com',
'https://www.myapp.com',
'https://staging.myapp.com',
];
if (env.ENVIRONMENT === 'development') {
ALLOWED_ORIGINS.push('http://localhost:3000');
}
function corsHeaders(request: Request): HeadersInit {
const origin = request.headers.get('Origin') || '';
if (!ALLOWED_ORIGINS.includes(origin)) {
return {};
}
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400',
};
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: corsHeaders(request),
});
}
const response = await handleRequest(request, env);
const headers = new Headers(response.headers);
for (const [key, value] of Object.entries(corsHeaders(request))) {
headers.set(key, value);
}
return new Response(response.body, {
status: response.status,
headers,
});
}
};
Express.js
BAD:
import cors from 'cors';
app.use(cors());
GOOD:
import cors from 'cors';
const ALLOWED_ORIGINS = [
'https://myapp.com',
'https://www.myapp.com',
];
app.use(cors({
origin: (origin, callback) => {
if (!origin) return callback(null, true);
if (ALLOWED_ORIGINS.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400,
}));
Next.js API Routes
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const ALLOWED_ORIGINS = [
'https://myapp.com',
'https://www.myapp.com',
];
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin') || '';
const isAllowed = ALLOWED_ORIGINS.includes(origin);
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': isAllowed ? origin : '',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400',
},
});
}
const response = NextResponse.next();
if (isAllowed) {
response.headers.set('Access-Control-Allow-Origin', origin);
response.headers.set('Access-Control-Allow-Credentials', 'true');
}
return response;
}
export const config = { matcher: '/api/:path*' };
Laravel
return [
'paths' => ['api/*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_origins' => [
'https://myapp.com',
'https://www.myapp.com',
],
'allowed_origins_patterns' => [],
'allowed_headers' => ['Content-Type', 'Authorization'],
'exposed_headers' => [],
'max_age' => 86400,
'supports_credentials' => true,
];
Python FastAPI
from fastapi.middleware.cors import CORSMiddleware
ALLOWED_ORIGINS = [
"https://myapp.com",
"https://www.myapp.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
max_age=86400,
)
CORS Checklist
PDPA 2024 Relevance
Misconfigured CORS that allows any website to access authenticated API endpoints constitutes a failure to implement langkah keselamatan yang munasabah (reasonable security measures). If user data is exfiltrated through CORS misconfiguration, the developer faces liability under Seksyen 5(1).