Pertanyaan dan Kapan Ini Berlaku
Halaman berjalan di https://app.example.com, sedangkan API berada di https://api.example.com. Frontend mengirimkan preferensi pengguna:
await fetch("https://api.example.com/profile/preferences", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify({ compactMode: true }),
})Server saat ini mengembalikan respons berikut untuk OPTIONS:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POSTPOST langsung melalui curl berhasil, tetapi konsol peramban hanya melaporkan galat lintas-asal (cross-origin). Jelaskan:
- mengapa kedua subdomain tersebut masih berstatus lintas-asal;
- mengapa peramban mengirimkan OPTIONS terlebih dahulu dan kapan peramban akan melanjutkan dengan POST;
- apa yang salah dengan header respons saat ini dan bagaimana cara menerapkan perbaikan berprinsip least-privilege;
- tanggung jawab terpisah dari CORS, cookie, autentikasi, otorisasi, dan CSRF;
- bagaimana cara membuktikan perbaikan tersebut di developer tools dan pengujian otomatis.
Ini berguna untuk wawancara frontend dan full-stack. Ujiannya bukanlah apakah seseorang dapat menghafal serangkaian header. Melainkan apakah mereka dapat menelusuri balik dari fase jaringan peramban dan memisahkan empat pertanyaan: apakah permintaan terkirim, apakah kredensial disertakan, bolehkah JavaScript membaca respons, dan apakah aplikasi mengotorisasi operasi tersebut?
Apa yang Dinilai oleh Pewawancara
Pertama, kandidat harus mengidentifikasi asal (origin) secara tepat. Sebuah origin terdiri dari skema, host, dan port. Kedua URL sama-sama menggunakan HTTPS dan biasanya berstatus same-site, tetapi host-nya berbeda, sehingga permintaan tersebut berstatus same-site dan cross-origin. CORS berbasis origin; atribut cookie SameSite berbasis site. Keduanya tidak dapat dipertukarkan.
Kedua, kandidat harus memahami pemicu preflight. application/json bukanlah nilai request-header yang masuk dalam CORS-safelist, dan X-CSRF-Token bukanlah request header yang masuk dalam safelist. Oleh karena itu, peramban mengirimkan OPTIONS untuk menanyakan apakah metode dan header sebenarnya diizinkan. Jika preflight gagal, POST tidak dikirim.
Ketiga, kandidat harus menemukan dua kesalahan konfigurasi langsung. Respons dengan kredensial tidak dapat menggunakan Access-Control-Allow-Origin: *, dan respons preflight tidak mengizinkan Content-Type atau X-CSRF-Token. Mencantumkan POST di bawah metode yang diizinkan saja tidak cukup.
Terakhir, kandidat harus menjaga batasan keamanan. CORS mengontrol apakah peramban membagikan respons lintas-asal kepada skrip. Ini tidak menggantikan autentikasi, otorisasi, atau perlindungan CSRF. Server tidak boleh merefleksikan sembarang origin hanya untuk membungkam konsol atau mengizinkan setiap metode dan header.
Pertanyaan untuk Diklarifikasi Terlebih Dahulu
- Pada fase jaringan mana kegagalan terjadi? Tidak ada permintaan, hanya OPTIONS, atau POST selesai yang responsnya
tidak dapat diakses oleh JavaScript menunjukkan kegagalan yang berbeda.
- Apakah permintaan harus membawa cookie? Bearer token atau sumber daya publik anonim mengubah kebijakan
kredensial dan origin yang diizinkan.
- Origin frontend mana saja yang secara tepat diizinkan? Produksi, staging, dan pengembangan lokal memerlukan origin
eksplisit, termasuk skema dan port. “Domain perusahaan” tidak cukup presisi.
- Apakah gateway, middleware autentikasi, atau pengalihan (redirect) mencegat OPTIONS? Preflight biasanya tidak memiliki
kredensial pengguna, sehingga tidak boleh diwajibkan untuk lolos autentikasi login terlebih dahulu.
- Apa saja atribut Domain, Path, Secure, dan SameSite pada cookie? Lolos CORS tidak menjamin
bahwa peramban mengirimkan cookie.
- Apakah respons aktual dan galat semuanya membawa header CORS? Jika respons 401, 403, atau 500 kehilangan header origin
yang diizinkan, peramban mungkin menyembunyikan status aslinya di balik galat CORS generik.
- Apakah ada CDN atau cache bersama? Jika respons bervariasi berdasarkan Origin, kirimkan
Vary: Originsehingga respons satu origin
tidak digunakan kembali untuk origin lain.
Kerangka Jawaban 30 Detik
“Saya akan membagi ini menjadi empat gerbang: origin, izin preflight, pengiriman kredensial, dan pembagian respons. Host-nya berbeda, jadi permintaan tersebut berstatus cross-origin. Content-Type JSON dan X-CSRF-Token memicu preflight OPTIONS. Peramban mengirimkan POST hanya jika respons mengizinkan origin yang tepat, POST, dan kedua header tersebut. Karena fetch menggunakan credentials: include, Allow-Origin tidak boleh berupa wildcard. Setelah pencocokan allowlist yang tepat, server harus mengembalikan https://app.example.com, Allow-Credentials, dan Vary: Origin. OPTIONS itu sendiri tidak bergantung pada cookie login; POST tetap memerlukan autentikasi, otorisasi, dan validasi CSRF. Saya akan memverifikasi OPTIONS, POST, cookie, header respons, dan status aplikasi di panel Network, lalu menjalankan matriks pengujian untuk origin positif dan origin berbahaya.”
Pembahasan Mendalam Langkah demi Langkah
Langkah satu: memodelkan state machine yang benar-benar dijalankan oleh peramban.
Preflight kira-kira berbentuk:
OPTIONS /profile/preferences HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-csrf-tokenPeramban tidak menanyakan apakah frontend dan backend milik perusahaan yang sama. Peramban menanyakan apakah respons secara eksplisit mengizinkan Origin, metode, dan kumpulan header ini. Bukti berikut mengisolasi setiap fase:
| Bukti jaringan | Arti | Pemeriksaan berikutnya |
|---|---|---|
| Tidak ada OPTIONS atau POST | JavaScript tidak berjalan, atau aturan sebelumnya seperti CSP memblokir URL | Periksa pemanggilan dan konsol terlebih dahulu |
| Hanya OPTIONS yang gagal | POST tidak dikirim | Periksa status, pengalihan, dan header allow |
| OPTIONS berhasil; POST tidak memiliki cookie | Izin CORS lolos; pengiriman kredensial tidak | Periksa credentials dan atribut cookie |
| POST mengembalikan 401/403/500, tetapi skrip hanya melihat CORS | Respons galat tidak memiliki header CORS yang valid | Pertahankan status dan tambahkan header origin yang diizinkan |
| POST berhasil dan skrip dapat membacanya | Jalur CORS lolos | Verifikasi status aplikasi dan uji negatif keamanan |
Langkah dua: menurunkan kumpulan izin minimum dari permintaan.
Untuk satu frontend produksi pada pertanyaan tersebut, kembalikan:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token
Vary: OriginServer harus membandingkan Origin dengan allowlist yang tepat dan menuliskannya kembali hanya setelah cocok. Jangan menyalin Origin yang diberikan klien tanpa syarat. Jika lingkungan staging dan lokal juga diizinkan, cantumkan setiap origin lengkap di lingkungan yang sesuai. Pencocokan akhiran (suffix match) dapat menerima domain penyerang yang tampak serupa.
Wildcard dapat sesuai untuk sumber daya publik, anonim, dan tanpa kredensial. Endpoint preferensi pengguna ini membawa cookie dan melakukan operasi sensitif, sehingga memerlukan origin yang spesifik. Preflight OPTIONS tidak membawa cookie. Gateway dapat menanganinya sebelum autentikasi sambil tetap memvalidasi Origin, metode, dan header permintaan secara ketat.
Langkah tiga: periksa permintaan aktual dan kredensial secara terpisah.
Peramban mengirim POST hanya setelah preflight berhasil. credentials: "include" memungkinkan permintaan lintas-asal masuk ke pemrosesan kredensial, tetapi Domain, Path, Secure, SameSite, dan kebijakan privasi peramban tetap menentukan apakah cookie disertakan.
Kedua subdomain HTTPS ini biasanya berstatus same-site dan cross-origin. SameSite mungkin mengizinkan cookie sementara CORS tetap berlaku. “CORS lolos” tidak berarti “cookie terkirim,” dan “cookie terkirim” tidak berarti “skrip boleh membaca respons.” Periksa Request Cookies, status respons, dan header respons CORS sebagai fakta terpisah di panel Network.
Langkah empat: pisahkan CORS dari keamanan aplikasi.
CORS adalah protokol pembagian respons peramban, bukan kontrol akses sisi server. Curl dan klien HTTP sisi server tidak menerapkan kebijakan origin yang sama (same-origin policy) peramban. Pemanggilan curl yang berhasil membuktikan bahwa API dapat menangani permintaan tersebut, bukan bahwa jalur peramban sudah benar.
POST tetap harus:
- mengautentikasi pengguna;
- mengotorisasi pengguna tersebut untuk memodifikasi sumber daya target;
- memvalidasi token CSRF atau menggunakan pertahanan yang sama kuatnya;
- memvalidasi input dan mendefinisikan perilaku idempotensi atau pengiriman ganda;
- mengembalikan header CORS yang benar pada respons 401, 403, dan 500 untuk origin yang diizinkan agar frontend melihat galat yang sebenarnya.
“Halaman berbahaya tidak dapat membaca respons” tidak berarti “permintaan tidak memiliki efek samping.” Beberapa permintaan lintas-asal mirip-formulir tidak memerlukan preflight dan dapat mencapai server. Operasi penulisan sensitif tidak dapat mengandalkan CORS saja untuk perlindungan CSRF.
Langkah lima: buktikan batasan dengan pengujian positif dan negatif.
Setelah perbaikan, verifikasi setidaknya:
| Kasus | Hasil yang diharapkan |
|---|---|
| Origin yang diizinkan mengirimkan permintaan yang ditentukan | OPTIONS 204, lalu POST; skrip membaca respons sebenarnya |
| Origin yang diizinkan menghilangkan token CSRF | Konfigurasi protokol menentukan preflight; validasi aplikasi menolak penulisan |
| Origin yang tidak diizinkan mengirimkan permintaan yang sama | Tidak ada header CORS yang mengizinkan origin tersebut; preflight tidak lolos |
| Permintaan menggunakan metode atau header yang tidak diizinkan | Preflight gagal, POST tidak ada, dan status tidak berubah |
| Cookie login kedaluwarsa | POST mengembalikan 401 yang dapat dibaca dan frontend memulai alur login-nya |
| Server mengembalikan 500 | Frontend melihat 500 dan galat terstruktur, bukan pesan CORS generik |
| Permintaan berulang menggunakan cache preflight | Perilaku tetap benar; perubahan konfigurasi juga diuji dengan cache dingin |
Korelasikan ini dengan log server. Preflight yang gagal tidak boleh memiliki POST yang cocok dan tidak ada pembaruan preferensi. Log dapat mencatat Origin, metode, nama header, dan alasan penolakan, tetapi bukan nilai cookie atau token CSRF.
Contoh Jawaban yang Kuat
“Saya pertama-tama akan mengidentifikasi fase jaringan. app.example.com dan api.example.com memiliki host yang berbeda, jadi keduanya berstatus cross-origin meskipun same-site. POST menggunakan Content-Type JSON dan X-CSRF-Token, sehingga peramban pertama-tama mengirimkan permintaan OPTIONS tanpa cookie. Permintaan ini mendeklarasikan Origin, POST, dan dua header non-sederhana tersebut. Peramban mengirimkan POST hanya jika respons preflight mengizinkan ketiganya.
Konfigurasi saat ini memiliki dua cacat langsung. Pertama, frontend menetapkan credentials: include, sehingga respons berkredensial tidak dapat menggunakan wildcard untuk Access-Control-Allow-Origin. Kedua, respons tidak mencakup Access-Control-Allow-Headers: Content-Type, X-CSRF-Token. Saya akan memvalidasi Origin terhadap allowlist server yang tepat. Untuk https://app.example.com, saya akan mengembalikan nilai spesifik tersebut, Allow-Credentials, POST, kedua header yang diizinkan, dan Vary: Origin. OPTIONS dapat ditangani sebelum middleware autentikasi pengguna, tetapi origin, metode, atau header yang tidak diizinkan tetap ditolak.
Setelah preflight lolos, saya akan memeriksa Request Cookies pada POST. credentials: include diperlukan tetapi tidak cukup; Domain, Path, Secure, SameSite, dan kebijakan peramban tetap berlaku. POST harus terus mengautentikasi, mengotorisasi, dan memvalidasi CSRF karena CORS mengontrol pembagian respons, bukan kontrol akses.
Untuk verifikasi, saya akan mengonfirmasi di panel Network bahwa POST hanya muncul setelah OPTIONS berhasil, lalu memeriksa cookie, status sebenarnya, dan header respons. Saya akan menguji origin yang diizinkan, origin yang tidak diizinkan, metode yang tidak diizinkan, login yang kedaluwarsa, dan respons 500. Log server harus membuktikan bahwa preflight yang gagal tidak memiliki POST dan tidak ada perubahan status. Curl adalah baseline API, bukan pengganti bukti CORS peramban.”
Kesalahan Umum
- Menganggap same-site sebagai same-origin → Subdomain yang berbeda tetap memicu CORS → **Bandingkan skema, host,
dan port.**
- Mengasumsikan POST dalam Allow-Methods sudah cukup → Header non-sederhana tidak diizinkan → **Periksa Origin,
Method, dan Headers secara bersamaan.**
- Menggunakan
Allow-Origin: *dengan kredensial → Peramban menolak untuk mengekspos respons → **Kembalikan
origin allowlist yang tepat.**
- Merefleksikan setiap Origin yang diminta → Situs mana pun dapat memperoleh akses baca ke respons berkredensial → **Cocokkan
allowlist yang tepat terlebih dahulu.**
- Mengautentikasi OPTIONS sebagai pengguna yang login → Preflight biasanya tidak memiliki kredensial → **Lakukan
pemeriksaan preflight yang dibatasi sebelum autentikasi pengguna.**
- Menggunakan
mode: "no-cors"sebagai perbaikan → Frontend menerima respons buram (opaque response) yang tidak dapat dibaca → **Perbaiki
protokol CORS server.**
- Memperlakukan CORS sebagai perlindungan CSRF → Permintaan dapat mencapai server dan menimbulkan efek samping → **Pertahankan CSRF
dan pemeriksaan otorisasi pada operasi penulisan sensitif.**
- Menambahkan header CORS hanya ke respons 2xx → Respons 401 atau 500 tersamarkan sebagai galat lintas-asal → **Gunakan
header CORS yang konsisten pada galat untuk origin yang diizinkan.**
- Hanya menguji dengan curl → Curl tidak menerapkan kebijakan same-origin peramban → **Verifikasi dengan bukti
jaringan peramban yang sebenarnya.**
Pertanyaan Lanjutan
Mengapa permintaan dapat melewati OPTIONS dan tetap gagal CORS?
Permintaan yang menggunakan metode, header, dan Content-Type yang masuk safelist dapat dikirim secara langsung. Peramban tetap memeriksa header CORS respons aktual. Jika pembagian respons tidak diizinkan, JavaScript tidak dapat membacanya meskipun server mungkin telah memprosesnya. Bukti jaringan harus membedakan antara “terkirim tetapi tidak dibagikan” dari preflight yang gagal.
Mengapa Access-Control-Allow-Credentials tidak boleh muncul dalam permintaan preflight?
Ini adalah header respons server yang menunjukkan bahwa peramban boleh mengekspos respons aktual yang dibuat dalam mode kredensial. Klien menyatakan niatnya melalui mode kredensial Fetch; preflight itu sendiri tidak berisi kredensial pengguna. Respons aktual juga harus memenuhi ketentuan origin spesifik dan Allow-Credentials.
Masalah apa yang diselesaikan oleh Vary: Origin?
Ketika server memilih Access-Control-Allow-Origin berdasarkan Origin permintaan, cache bersama harus menyertakan Origin dalam kunci cache-nya. Jika tidak, respons untuk satu origin yang diizinkan dapat digunakan kembali untuk origin lain, menyebabkan penolakan atau pengungkapan data yang salah.
Mengapa cookie masih hilang setelah CORS diperbaiki?
Periksa credentials: "include", lalu periksa Domain, Path, Secure, SameSite, masa kedaluwarsa cookie, dan pembatasan privasi peramban. Izin CORS dan pengiriman cookie adalah gerbang yang berdampingan tetapi independen.
Bagaimana cara mendukung beberapa domain frontend yang sah?
Pertahankan allowlist yang tepat, validasi Origin permintaan lengkap, tulis kembali nilai yang cocok, dan kirimkan Vary: Origin. Jangan mengembalikan beberapa nilai Allow-Origin atau menggunakan ekspresi reguler yang permisif, pencocokan akhiran, atau refleksi tanpa syarat.
Bisakah frontend memperbaiki kegagalan CORS server dengan sendirinya?
Tidak. Frontend dapat menghapus header non-sederhana yang tidak perlu atau memilih mode kredensial yang dimaksudkan, tetapi API, reverse proxy, atau gateway harus mengembalikan header respons yang mengizinkan pembagian lintas-asal. Kode produksi tidak boleh bergantung pada ekstensi peramban atau menonaktifkan kontrol keamanan.
Berapa lama preflight harus di-cache?
Pilih durasi berdasarkan perputaran konfigurasi, kebutuhan pencabutan, dan batas peramban. Saat merilis perubahan CORS, uji cache dingin maupun jalur cache yang sudah ada. Caching mengurangi lalu lintas OPTIONS tetapi dapat memperpanjang izin yang salah, sehingga tidak boleh menyembunyikan cacat konfigurasi.