Pesan seperti Access to fetch has been blocked by CORS policy hampir pasti pernah muncul saat frontend dan backend berjalan di alamat berbeda. Misalnya, aplikasi JavaScript berada di http://localhost:3000, sementara API PHP berada di http://localhost:8000. Request terlihat sederhana, tetapi browser menolaknya sebelum hasil API bisa dibaca oleh JavaScript.
Reaksi yang sering terjadi adalah mencari cara untuk “mematikan CORS”. Ini pendekatan yang keliru. CORS, atau Cross-Origin Resource Sharing, bukan kerusakan pada aplikasi, melainkan mekanisme HTTP yang memungkinkan server menyatakan origin mana yang boleh membaca responsnya. Mekanisme ini bekerja bersama same-origin policy, aturan browser yang membatasi akses antar-situs. MDN menjelaskan bahwa origin ditentukan oleh kombinasi protokol, hostname, dan port.
Kenapa frontend bisa ditolak browser?
Dua alamat berikut terlihat berasal dari komputer yang sama, tetapi tetap dianggap berbeda:
http://localhost:3000http://localhost:8000
Hostname-nya sama, tetapi port-nya berbeda. Begitu juga http://example.com dan https://example.com: protokolnya berbeda sehingga origin-nya berbeda.
Browser tetap dapat mengirim beberapa jenis request lintas origin, misalnya melalui form HTML. Namun, browser membatasi kemampuan JavaScript untuk membaca respons dari origin lain. Tanpa pembatasan ini, sebuah situs jahat bisa mencoba membaca data dari aplikasi lain yang sedang dibuka pengguna.
Di sinilah CORS bekerja. Server mengirim header tertentu untuk menyatakan, “origin ini boleh membaca respons saya.” Jika header tidak ada atau nilainya tidak sesuai, browser memblokir akses dari kode JavaScript. Server mungkin sebenarnya sudah mengirim respons, tetapi respons itu tidak diberikan kepada aplikasi frontend.
Memahami preflight tanpa istilah yang menakutkan
Untuk request tertentu, browser mengirim request pendahuluan bernama preflight. Request ini menggunakan metode OPTIONS dan bertanya kepada server apakah metode serta header yang akan dipakai diperbolehkan.
Contohnya, frontend mengirim request berikut:
fetch('https://api.example.com/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ product_id: 12 })
});Karena memakai metode POST dengan JSON dan header otorisasi, browser bisa mengirim preflight lebih dulu. Server API perlu menjawab dengan header yang sesuai, misalnya:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, AuthorizationJika server tidak menangani OPTIONS, mengembalikan status error, atau tidak mengizinkan header yang diminta, request utama tidak akan dilanjutkan. Dokumentasi CORS MDN menjelaskan bahwa preflight digunakan browser untuk memeriksa metode dan header sebelum request yang berpotensi menimbulkan perubahan data.
Jangan langsung memakai Access-Control-Allow-Origin: *
Wildcard * memang praktis untuk API publik yang tidak memakai kredensial pengguna. Namun, wildcard bukan pilihan aman untuk semua kasus.
Misalnya, API dashboard mengembalikan data pesanan berdasarkan cookie login. Jika server mengizinkan semua origin secara sembarangan, situs lain berpotensi mencoba mengakses endpoint tersebut dari browser korban. Risiko menjadi lebih serius jika server juga mengizinkan kredensial.
Untuk aplikasi yang memakai cookie atau autentikasi berbasis kredensial, gunakan origin yang spesifik:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: trueJangan menggabungkan Access-Control-Allow-Credentials: true dengan Access-Control-Allow-Origin: *. Browser tidak menganggap kombinasi tersebut valid untuk request berkredensial. Untuk API publik tanpa data pribadi, wildcard dapat digunakan, tetapi pastikan endpoint tersebut memang boleh dibaca siapa saja.
OWASP juga menyarankan agar konfigurasi CORS dibuat sespesifik mungkin dan tidak digunakan bila komunikasi lintas origin memang tidak dibutuhkan. CORS bukan pengganti autentikasi dan otorisasi. Server tetap harus memeriksa token, hak akses, serta kepemilikan data pada setiap endpoint.
Contoh konfigurasi sederhana di PHP
Untuk API PHP sederhana, konfigurasi dapat dimulai seperti ini:
<?php
$allowedOrigin = 'https://app.example.com';
if (isset($_SERVER['HTTP_ORIGIN']) &&
$_SERVER['HTTP_ORIGIN'] === $allowedOrigin) {
header("Access-Control-Allow-Origin: $allowedOrigin");
header('Access-Control-Allow-Credentials: true');
header('Vary: Origin');
}
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}Contoh tersebut bukan konfigurasi universal. Pada proyek nyata, daftar origin sebaiknya disimpan dalam konfigurasi aplikasi, bukan ditulis berulang di banyak file. Jika ada beberapa frontend yang sah, cocokkan nilai Origin dengan allowlist yang jelas. Jangan sekadar memantulkan nilai Origin dari request tanpa validasi.
Header Vary: Origin juga penting ketika server memilih respons berdasarkan origin. Header ini memberi tahu cache bahwa respons bisa berbeda untuk nilai Origin yang berbeda.
Cara membaca error CORS dengan lebih tenang
Ketika CORS gagal, jangan hanya melihat baris error di frontend. Buka tab Network pada DevTools browser, lalu periksa beberapa hal berikut:
- Apakah request
OPTIONSmuncul sebelum request utama? - Berapa status code respons preflight?
- Apakah
Access-Control-Allow-Originsesuai dengan origin frontend, termasuk protokol dan port? - Apakah metode pada
Access-Control-Allow-Methodsmencakup metode yang dipakai? - Apakah semua header yang diminta tercantum di
Access-Control-Allow-Headers? - Apakah request memakai cookie atau kredensial, tetapi server belum mengaktifkannya?
Jika API berada di balik Nginx, reverse proxy, CDN, atau gateway, periksa juga lapisan tersebut. Bisa saja PHP sudah mengirim header yang benar, tetapi proxy menghapusnya atau tidak meneruskan request OPTIONS.
Apa artinya bagi kita?
CORS sebaiknya diperlakukan sebagai bagian dari desain API, bukan tambalan setelah frontend selesai. Tentukan sejak awal frontend mana yang boleh mengakses API, endpoint mana yang bersifat publik, dan apakah autentikasi menggunakan cookie atau token.
Yang bisa dilakukan sekarang adalah membuat allowlist origin untuk lingkungan development dan production, menangani request OPTIONS secara eksplisit, menguji request berkredensial, serta memastikan autentikasi tetap diperiksa di sisi server. Dengan begitu, solusi CORS bukan hanya membuat error hilang, tetapi juga menjaga agar API tetap bekerja tanpa membuka akses lebih luas dari yang diperlukan.
Sumber & bacaan lebih lanjut
- Cross-Origin Resource Sharing (CORS) - MDN Web Docs
- Same-origin policy - MDN Web Docs
- Cross-Origin Resource Sharing (CORS) configuration - MDN Web Docs
- REST Security Cheat Sheet - OWASP
– Rio Yotto @rioyotto
