Home / Artikel / Web Development
Web Development

CORS Bukan Bug Misterius: Cara Memahami dan Memperbaiki Akses API dari Browser

Pesan “blocked by CORS policy” sering membuat developer mengira API rusak. Padahal, masalahnya biasanya ada pada aturan browser tentang siapa yang boleh membaca respons, bukan pada koneksi server semata.

CORS Bukan Bug Misterius: Cara Memahami dan Memperbaiki Akses API dari Browser

Pesan blocked by CORS policy hampir pasti pernah muncul ketika frontend dan backend dikembangkan di alamat yang berbeda. Misalnya, aplikasi JavaScript berjalan di http://localhost:3000, sementara API PHP berada di http://localhost:8000. Server bisa saja hidup, endpoint bisa dibuka dengan Postman, tetapi browser tetap menolak responsnya.

Memahami CORS penting karena solusi yang asal—seperti mengizinkan semua origin atau mematikan pemeriksaan browser—dapat membuat aplikasi berjalan cepat saat development, tetapi meninggalkan celah keamanan saat dipasang ke publik.

Apa sebenarnya CORS?

CORS, atau Cross-Origin Resource Sharing, adalah mekanisme yang mengatur apakah halaman web dari satu origin boleh mengakses resource dari origin lain. Origin terdiri dari kombinasi protokol, host, dan port.

Artinya, alamat berikut dianggap berbeda meskipun sama-sama berjalan di komputer sendiri:

  • http://localhost:3000
  • http://localhost:8000
  • https://localhost:3000

Perbedaan port dan protokol saja sudah cukup membuat origin berubah. Browser menerapkan aturan ini untuk melindungi pengguna. Tanpa pembatasan tersebut, sebuah situs berbahaya berpotensi mengirim permintaan ke layanan lain yang sedang dibuka pengguna dan membaca datanya.

Jadi, CORS bukan sistem keamanan yang berdiri di sisi server saja. Ia adalah aturan yang terutama ditegakkan oleh browser ketika JavaScript mencoba membaca respons lintas origin.

Kenapa API bisa berhasil di Postman tetapi gagal di browser?

Postman, cURL, dan tool backend biasanya tidak menerapkan kebijakan keamanan browser yang sama. Ketika request berhasil di Postman, itu hanya menunjukkan bahwa server menerima permintaan tersebut. Belum tentu browser diizinkan membaca responsnya.

Contohnya, endpoint API mungkin mengembalikan status 200 OK, tetapi tidak menyertakan header:

Access-Control-Allow-Origin: http://localhost:3000

Browser menerima respons dari jaringan, lalu memblokir akses JavaScript terhadap isi respons tersebut. Dari sudut pandang developer, hasilnya terlihat seperti request gagal, padahal server mungkin sudah memprosesnya.

Bedakan simple request dan preflight

Tidak semua request lintas origin diperlakukan dengan cara yang sama. Sebagian request sederhana dapat langsung dikirim. Namun, request dengan metode atau header tertentu akan didahului pemeriksaan bernama preflight.

Preflight adalah request OPTIONS yang dikirim browser untuk bertanya kepada server: apakah origin ini boleh menggunakan metode dan header yang diminta?

Misalnya frontend mengirim request:

fetch('https://api.contoh.com/orders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token'
  },
  body: JSON.stringify({ product_id: 10 })
});

Karena memakai metode POST, header khusus, dan format JSON, browser mungkin mengirim OPTIONS terlebih dahulu. Server harus menjawabnya dengan benar, termasuk izin terhadap origin, metode, dan header.

Header yang biasanya diperlukan

Implementasi CORS berbeda-beda tergantung framework, tetapi konsep dasarnya sama. Server perlu menjelaskan aturan akses melalui header HTTP.

Access-Control-Allow-Origin: https://app.contoh.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

Jika aplikasi memakai cookie atau session browser, biasanya diperlukan juga:

Access-Control-Allow-Credentials: true

Namun, ketika Access-Control-Allow-Credentials: true digunakan, nilai Access-Control-Allow-Origin tidak boleh berupa tanda bintang (*). Server harus menyebutkan origin secara spesifik.

Contoh penanganan di PHP

Untuk API sederhana berbasis PHP, aturan CORS dapat ditempatkan sebelum output apa pun dikirim:

<?php
$allowedOrigin = 'http://localhost:3000';

if (isset($_SERVER['HTTP_ORIGIN']) && $_SERVER['HTTP_ORIGIN'] === $allowedOrigin) {
    header("Access-Control-Allow-Origin: $allowedOrigin");
    header('Access-Control-Allow-Credentials: true');
}

header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit;
}

Contoh ini hanya mengizinkan satu origin. Dalam aplikasi nyata, daftar origin bisa disimpan dalam konfigurasi, lalu dibandingkan dengan nilai header Origin. Hindari menerima nilai origin dari request dan langsung memantulkannya tanpa validasi.

Kesalahan yang sering terjadi

Menggunakan wildcard untuk semua kondisi

Konfigurasi seperti Access-Control-Allow-Origin: * memang praktis untuk API publik yang tidak memakai kredensial. Namun, konfigurasi ini tidak cocok untuk endpoint yang mengandalkan cookie, session, atau data pengguna.

Hanya menangani GET dan POST

Developer sering menambahkan header CORS pada endpoint utama, tetapi lupa menangani request OPTIONS. Akibatnya, request aktual tidak pernah dijalankan karena preflight sudah gagal lebih dulu.

Menambahkan header di tempat yang salah

Header harus dikirim sebelum server mengeluarkan HTML, spasi, atau pesan error. Pada PHP, output kecil sebelum pemanggilan header() dapat menyebabkan header tidak lagi bisa diubah.

Mengira CORS dapat memperbaiki autentikasi

CORS hanya mengatur apakah browser boleh membaca respons lintas origin. Ia bukan pengganti autentikasi, otorisasi, validasi input, CSRF protection, atau pembatasan akses di server.

Cara debugging yang lebih terarah

  1. Periksa origin frontend. Catat protokol, domain, dan port yang benar-benar digunakan browser.
  2. Buka tab Network. Cari request API dan lihat apakah ada request OPTIONS sebelum request utama.
  3. Periksa response header. Pastikan Access-Control-Allow-Origin sesuai dengan origin frontend.
  4. Periksa metode dan header. Jika frontend memakai Authorization atau Content-Type: application/json, server harus mengizinkannya.
  5. Uji tanpa menyimpulkan dari Postman. Postman berguna memeriksa API, tetapi tidak bisa memastikan perilaku browser.

Apa artinya bagi kita?

CORS sebaiknya diperlakukan sebagai bagian dari desain API, bukan tambalan setelah error muncul. Tentukan sejak awal frontend mana yang boleh mengakses API, apakah autentikasi memakai cookie atau token, dan metode apa saja yang diperlukan.

Untuk development, izinkan origin lokal secara eksplisit. Untuk production, gunakan daftar domain yang benar-benar dipercaya. Jangan menjadikan * sebagai solusi permanen hanya karena membuat error hilang.

Jika sebuah API memang ditujukan untuk publik, CORS yang longgar bisa masuk akal—tetapi endpoint tetap perlu memiliki validasi, rate limiting, autentikasi bila diperlukan, dan perlindungan terhadap penyalahgunaan. CORS menyelesaikan masalah izin pembacaan oleh browser; ia tidak menyelesaikan seluruh masalah keamanan aplikasi web.

– Rio Yotto @rioyotto