CORS (Cross-Origin Resource Sharing) adalah mekanisme keamanan browser yang mengontrol origin mana yang dapat membuat permintaan ke API Anda. Ini juga merupakan salah satu kontrol keamanan yang paling disalahpahami — developer sering cargo-cult konfigurasi dari StackOverflow tanpa memahami apa yang mereka izinkan. Miskonfigurasi CORS paling berbahaya yang saya lihat secara reguler: `Access-Control-Allow-Origin: *` dikombinasikan dengan `Access-Control-Allow-Credentials: true`.
CORS adalah mekanisme penegakan browser. Ini TIDAK melindungi API Anda dari klien HTTP langsung (curl, Postman, permintaan server-ke-server) — yang mengabaikan header CORS sepenuhnya. CORS hanya membatasi permintaan lintas origin berbasis browser. Yang dilindunginya: cross-site request forgery (CSRF) untuk permintaan yang memiliki kredensial, situs jahat yang membaca respons API Anda menggunakan sesi browser korban.
Spesifikasi CORS browser secara eksplisit melarang `Access-Control-Allow-Origin: *` ketika `Access-Control-Allow-Credentials: true`. Jika API Anda menyajikan cookie atau header Authorization pada permintaan lintas origin dengan kredensial, Anda harus menentukan origin yang tepat. Ini berarti konfigurasi CORS Anda harus mempertahankan daftar origin yang diizinkan dan merespons secara dinamis dengan origin yang meminta jika ada dalam daftar.
// NestJS main.ts — correct CORS configuration
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') ?? []
// e.g. ALLOWED_ORIGINS=https://app.yourdomain.com,https://admin.yourdomain.com
app.enableCors({
origin: (requestOrigin, callback) => {
// Allow requests with no origin (mobile apps, curl, Postman)
if (!requestOrigin) return callback(null, true)
if (allowedOrigins.includes(requestOrigin)) {
callback(null, true)
} else {
callback(new Error(`CORS: origin ${requestOrigin} not allowed`))
}
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
credentials: true, // required for cookies/auth headers
maxAge: 600, // cache preflight for 10 minutes
})
await app.listen(3000)
}
bootstrap()
// ❌ NEVER do this — allows any origin with credentials
// app.enableCors({ origin: '*', credentials: true })
// Browser rejects this combination, but the intent is a critical vuln
// Next.js API route CORS (App Router)
// app/api/data/route.ts
export async function GET(request: Request) {
const origin = request.headers.get('origin') ?? ''
const allowed = process.env.ALLOWED_ORIGINS?.split(',') ?? []
const headers = new Headers()
if (allowed.includes(origin)) {
headers.set('Access-Control-Allow-Origin', origin)
headers.set('Access-Control-Allow-Credentials', 'true')
headers.set('Vary', 'Origin')
}
headers.set('Access-Control-Max-Age', '600')
return Response.json({ data: 'ok' }, { headers })
}
export async function OPTIONS(request: Request) {
const origin = request.headers.get('origin') ?? ''
const allowed = process.env.ALLOWED_ORIGINS?.split(',') ?? []
const headers = new Headers()
if (allowed.includes(origin)) {
headers.set('Access-Control-Allow-Origin', origin)
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
headers.set('Access-Control-Allow-Credentials', 'true')
headers.set('Access-Control-Max-Age', '600')
}
return new Response(null, { status: 204, headers })
}Dari pengalaman saya mengkonfigurasi CORS untuk API ERP: pertahankan daftar origin yang diizinkan dalam variabel lingkungan, bukan hardcoded. Untuk lingkungan staging, daftarnya mencakup localhost:3000, localhost:3001, dan domain staging. Untuk produksi, hanya domain frontend produksi. Ini mencegah kesalahan umum mendeploy konfigurasi CORS staging ke produksi.
NestJS menggunakan paket cors (middleware cors Express) di bawahnya. Konfigurasikan di main.ts dengan validasi origin eksplisit, batas metode, dan penanganan kredensial. Kunci adalah pola callback origin — ini memungkinkan Anda memeriksa origin yang meminta terhadap allowlist Anda dan mengembalikan error untuk origin yang tidak dikenal, daripada mengizinkan semua atau memblokir semua.
Permintaan preflight (OPTIONS) dikirim oleh browser sebelum permintaan lintas origin dengan kredensial atau non-sederhana. Setiap preflight menambahkan hit latensi round-trip. Konfigurasikan Access-Control-Max-Age untuk meng-cache hasil preflight — browser tidak akan mengirim ulang permintaan OPTIONS selama durasi ini. Saya menggunakan 600 detik (10 menit).
Miskonfigurasi CORS yang muncul dalam laporan penetration test nyata: menggunakan regex yang cocok dengan origin terlalu luas. Saya telah melihat konfigurasi seperti `origin.includes('mycompany.com')` yang akan mengizinkan `evil-mycompany.com` masuk. Selalu gunakan pencocokan string tepat atau sekumpulan origin yang divalidasi. Kesalahan umum lainnya: tidak membatasi metode yang diizinkan.
Untuk API routes Next.js, header CORS tidak diatur secara otomatis — Anda perlu menambahkannya secara eksplisit. Pola yang direkomendasikan adalah pembungkus middleware CORS yang memvalidasi origin dan menetapkan header yang sesuai. Jika aplikasi Next.js dan API Anda berada di origin yang sama (yang seharusnya dalam sebagian besar kasus), Anda tidak memerlukan CORS sama sekali.
blog.posts.corsConfiguration.content.section4Content
CORS dan atribut cookie SameSite bekerja bersama untuk keamanan kredensial. Dengan cookie SameSite=Strict, browser tidak akan mengirim cookie pada permintaan lintas origin mana pun, membuat konfigurasi kredensial CORS tidak relevan untuk cookie tersebut. Kombinasi yang saya gunakan untuk API ERP: refresh token dalam cookie httpOnly Strict Secure (same-origin saja), access token dalam memori (bukan cookie), allowlist CORS untuk origin frontend tertentu.
Sumber & Bacaan Lanjutan