Apa Itu CORS? Origin, Preflight, Credentials, dan Konfigurasi Aman
Penjelasan CORS yang membedakan same-origin policy, preflight, credentials, CSRF, cache, dan konfigurasi server yang aman.
0xNN · · 6 min read
Apa Itu CORS? Origin, Preflight, Credentials, dan Konfigurasi Aman
Pesan seperti ini muncul ketika JavaScript di browser tidak diizinkan membaca response lintas origin:
Access to fetch at 'https://api.example.com/data'
from origin 'https://app.example.com'
has been blocked by CORS policy
CORS atau Cross-Origin Resource Sharing adalah protokol berbasis HTTP header. Server menyatakan origin mana yang boleh membaca response melalui JavaScript browser.
Hal terpenting: CORS bukan autentikasi, bukan otorisasi, dan bukan perlindungan CSRF yang lengkap.
---
Apa yang Disebut Origin?
Origin terdiri dari scheme, hostname, dan port:
https://app.example.com
Perubahan salah satu bagian menghasilkan origin berbeda:
http://app.example.com # scheme berbeda
https://api.example.com # hostname berbeda
https://app.example.com:8443 # port berbeda
Path tidak termasuk origin. /dashboard dan /settings pada scheme, host, dan port yang sama tetap same-origin.
Same-origin policy membatasi cara dokumen atau script dari satu origin berinteraksi dengan resource origin lain. CORS memberi mekanisme bagi server untuk membuka akses baca tertentu.
---
Request Sederhana dan Preflight
Untuk request tertentu, browser langsung mengirim request dengan header Origin:
GET /profile HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Server mengizinkan origin tersebut:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Content-Type: application/json
Jika server memilih origin secara dinamis, Vary: Origin membantu cache membedakan response untuk origin berbeda.
Request dengan method atau header tertentu memicu preflight. Browser mengirim OPTIONS sebelum request utama:
OPTIONS /profile HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, authorization
Response preflight dapat berisi:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Vary: Origin
Jika preflight gagal, browser tidak mengirim request utama. Namun tidak semua cross-origin request memakai preflight, jadi preflight tidak boleh dianggap sebagai access control utama.
---
Credentials dan Wildcard
Cookie atau HTTP authentication lintas origin memerlukan opt-in di client:
const response = await fetch('https://api.example.com/profile', {
credentials: 'include',
});
Server juga harus mengizinkan credentials:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Untuk response credentialed, Access-Control-Allow-Origin: * tidak dapat digunakan sebagai pengganti origin eksplisit.
Cookie tetap tunduk pada atribut SameSite, Secure, domain, dan path. CORS tidak menggantikan konfigurasi cookie atau proteksi CSRF.
---
CORS Tidak Mencegah Request Berbahaya
Kesalahpahaman umum adalah “tanpa CORS, situs jahat tidak bisa mengirim request”. Same-origin policy terutama membatasi pembacaan response oleh script. Beberapa request lintas origin masih dapat dikirim melalui form, image, link, atau request sederhana.
Endpoint yang mengubah state tetap membutuhkan:
• autentikasi;
• otorisasi per resource;
• CSRF protection jika memakai cookie;
• validasi input;
• metode HTTP dan content type yang tepat;
• audit dan rate limiting bila relevan.
Jangan memakai header Origin sebagai satu-satunya bukti identitas pengguna.
---
Konfigurasi Express dengan Allowlist
Middleware cors dapat memakai daftar origin:
import cors from 'cors';
import express from 'express';
const app = express();
const allowedOrigins = new Set([
'https://msncode.dev',
'https://admin.msncode.dev',
]);
app.use(cors({
origin(origin, callback) {
if (!origin) {
return callback(null, true);
}
if (allowedOrigins.has(origin)) {
return callback(null, true);
}
return callback(new Error('origin not allowed'));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
Request tanpa Origin dapat datang dari server-to-server client, CLI, atau tool lain. Keputusan mengizinkannya harus mengikuti autentikasi endpoint, bukan asumsi bahwa request tersebut pasti aman.
Hindari pengecekan substring seperti origin.endsWith('example.com') tanpa boundary yang benar; evil-example.com dapat ikut cocok. Parse origin sebagai URL dan bandingkan hostname/port secara eksplisit, atau gunakan exact allowlist.
---
Frontend Tidak Bisa Memberi Izin kepada Dirinya Sendiri
Menambahkan Access-Control-Allow-Origin pada request frontend tidak membantu. Header itu harus ada pada response server.
mode: 'no-cors' juga bukan solusi untuk membaca JSON:
await fetch(url, { mode: 'no-cors' });
Response menjadi opaque sehingga status, header, dan body tidak dapat dibaca oleh JavaScript.
Development proxy dapat membuat browser melihat request sebagai same-origin, tetapi proxy tersebut menjadi komponen server yang meneruskan request. Production tetap perlu arsitektur dan access control yang benar.
---
Error Response Juga Membutuhkan Header CORS
Konfigurasi sering hanya menambahkan header pada response sukses. Akibatnya, error autentikasi atau error server terlihat sebagai “CORS error” dan body diagnostik tidak dapat dibaca browser.
Pastikan layer CORS dijalankan sebelum route dan error handler yang relevan. Uji setidaknya:
• response sukses;
• validasi gagal;
• token tidak ada atau kedaluwarsa;
• route tidak ditemukan;
• rate limit;
• error internal yang sudah disanitasi.
Jangan membuka detail stack trace hanya agar debugging frontend lebih mudah. Response error tetap harus aman untuk pengguna, sementara detail internal masuk ke observability server dengan request ID.
---
Cache, CDN, dan Origin Dinamis
Jika server memantulkan origin yang ada di allowlist:
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Vary: Origin memberi tahu cache bahwa response dapat berbeda berdasarkan Origin. Tanpanya, CDN atau reverse proxy berisiko melayani header untuk origin A kepada origin B.
Periksa konfigurasi cache preflight dan response utama secara terpisah. Access-Control-Max-Age mengurangi jumlah preflight, tetapi perubahan allowlist mungkin tidak langsung terlihat pada browser yang masih menyimpan hasil lama. Pilih durasi yang seimbang dengan kebutuhan perubahan kebijakan.
Origin null dapat muncul dari sandboxed iframe, file lokal, atau konteks tertentu. Jangan otomatis memasukkan null ke allowlist tanpa memahami sumber request.
---
API Publik Tetap Membutuhkan Batas
Access-Control-Allow-Origin: * masuk akal untuk resource yang memang publik dan tidak memakai credentials, misalnya font publik atau dataset yang boleh dibaca siapa pun. Wildcard tidak dengan sendirinya membuat data privat menjadi publik—data privat seharusnya sudah dilindungi autentikasi dan otorisasi—tetapi ia memperluas siapa yang dapat membaca response melalui browser.
API publik tetap membutuhkan validasi, rate limit, proteksi abuse, dan budget operasional. CORS hanya menentukan kemampuan pembacaan oleh browser; script server, bot, dan CLI tidak tunduk pada pembatasan browser ini.
---
Checklist Debugging
1. Bandingkan scheme, hostname, dan port frontend serta API.
2. Lihat request OPTIONS dan response-nya di Network panel.
3. Pastikan origin response sama persis dengan origin frontend.
4. Pastikan method dan request header diizinkan.
5. Untuk cookie, periksa credentials, allow-credentials, dan atribut cookie.
6. Pastikan proxy/CDN tidak menghapus header atau cache response origin secara salah.
7. Uji otorisasi dan CSRF secara terpisah—CORS yang berhasil tidak membuktikan API aman.
Referensi
• WHATWG Fetch Standard: CORS protocol
• MDN: Cross-Origin Resource Sharing
• MDN: Same-origin policy
• Express CORS middleware