Web Security
Memahami CSRF dan Cara Efektif Mencegah Serangannya
Daftar isi
- Kenapa Topik Ini Penting Buat Dipahami
- Bagaimana Sebenarnya CSRF Bekerja
- Kenapa Serangan Ini Licik
- Getting Started: Menyiapkan Proyek Contoh
- Step 1: Inisialisasi Proyek
- Step 2: Bikin Server dengan Endpoint yang Rentan
- Step 3: Menambal dengan CSRF Token (Synchronizer Token Pattern)
- Kesalahan Umum di Tahap Ini
- Step 4: Menambahkan SameSite Cookie
- Step 5: Verifikasi Header Origin dan Referer
- Step 6: Menguji Proteksi yang Sudah Dipasang
- Contoh di Framework Lain: Django
- Perbandingan Metode Pencegahan CSRF
- Kesalahan yang Sering Terjadi dan Cara Mengatasinya
- 1. Token CSRF Hilang Setelah Refresh atau Buka Tab Baru
- 2. Request AJAX Selalu Kena 403
- 3. SameSite=Strict Bikin Fitur Login Lewat Link Email Rusak
- 4. Proteksi CSRF Malah Diaktifkan di Endpoint Publik
- Studi Kasus Singkat: Menambal Endpoint Transfer di Aplikasi Sederhana
- Pertanyaan yang Sering Muncul
- Menerapkan Proteksi CSRF di Laravel
- Menerapkan Proteksi CSRF di Spring Boot
- Double Submit Cookie: Opsi buat API Tanpa Session Server
- Tools yang Layak Dicoba buat Ngecek Celah CSRF
- OWASP ZAP
- Burp Suite Community Edition
- csurf dan Alternatifnya di Node.js
- Helmet.js
- Snyk dan GitHub CodeQL
- Kenapa Web Application Firewall Bukan Solusi Ajaib
- CSRF di Dunia SPA dan Mobile App, Masih Relevan Nggak?
- Cerita dari Lapangan: Audit CSRF di Platform Marketplace Lokal
- Soal Regulasi: Kenapa CSRF Juga Urusan Kepatuhan di Indonesia
- Checklist Audit CSRF Sebelum Rilis ke Production
- Beberapa Pertanyaan Tambahan yang Sering Muncul
- Rekomendasi Sesuai Skala Tim dan Anggaran
- Proteksi CSRF di WordPress: Nonce yang Sering Disepelekan
- Next.js dan Framework SSR Modern, Jangan Kira Otomatis Aman
- GraphQL dan CSRF: Satu Pintu, Risikonya Nggak Kalah Besar
- csrf-sync: Opsi Lain Selain Package csrf
- Menulis Automated Test Biar Proteksi CSRF Nggak Regresi Diam-Diam
- Kapan Proteksi CSRF Boleh Sedikit Dilonggarkan?
- Kesimpulan
CSRF (Cross-Site Request Forgery) itu salah satu kerentanan web yang jadul sebenernya, tapi anehnya masih sering muncul di aplikasi-aplikasi yang dibangun sekarang. Kalau Teman-Teman lagi belajar keamanan web atau baru mau mengamankan aplikasi yang sedang dibangun, artikel ini bakal mengajak Teman-Teman paham dari akar masalahnya, lihat contoh kodenya, sampai praktik langsung bikin proteksinya.
CSRF adalah serangan yang mengelabui browser pengguna yang sudah login di sebuah situs, sehingga browser itu tanpa sadar mengirim permintaan (request) berbahaya ke situs tersebut atas nama pengguna, tanpa persetujuan mereka.
Yang bikin CSRF berbahaya bukan karena penyerang mencuri password atau data pengguna secara langsung, tapi karena penyerang "meminjam" sesi login yang sudah aktif. Selama browser korban masih menyimpan cookie sesi, situs target akan menganggap permintaan palsu itu datang dari pengguna yang sah.
Kenapa Topik Ini Penting Buat Dipahami
Bayangkan begini: Teman-Teman lagi buka tab internet banking buat cek saldo, terus di tab sebelah iseng klik link promo dari grup WhatsApp. Kalau situs banking itu nggak punya proteksi CSRF yang layak, halaman promo tadi bisa saja diam-diam mengirim perintah transfer dana ke rekening penyerang, dan bank akan menganggapnya sah karena sesi login Teman-Teman masih aktif.
Kasus semacam ini bukan cerita fiksi. CSRF sudah lama masuk daftar kerentanan yang dibahas serius oleh komunitas keamanan web, termasuk OWASP, organisasi nirlaba yang jadi rujukan utama soal keamanan aplikasi web di seluruh dunia. Aplikasi finansial, e-commerce, dan sistem manajemen konten adalah target favorit karena punya banyak aksi yang mengubah data atau memindahkan uang.
Sebelum lanjut ke praktik, ada beberapa hal yang sebaiknya Teman-Teman sudah kenal supaya tutorial ini nyambung:
-
Paham dasar HTTP, terutama method GET dan POST
-
Pernah bikin server sederhana dengan Node.js atau bahasa backend lain
-
Ngerti konsep cookie dan session, minimal secara garis besar
-
Terinstal Node.js di komputer (versi 18 ke atas lebih aman)
-
Editor kode dan terminal buat menjalankan perintah
Kalau semua poin di atas sudah aman, kita bisa lanjut.
Bagaimana Sebenarnya CSRF Bekerja
![]()
Supaya proteksinya masuk akal, Teman-Teman perlu ngerti dulu urutan kejadian saat serangan CSRF berlangsung. Alurnya kurang lebih begini:
-
Pengguna login ke situs asli, misalnya
bank.com, dan browser menyimpan cookie sesi. -
Tanpa logout, pengguna membuka halaman lain yang dikendalikan penyerang, entah lewat email phishing, iklan, atau forum.
-
Halaman jahat itu punya form tersembunyi yang otomatis mengirim request ke
bank.com. -
Browser, karena sifatnya memang begitu, otomatis menyertakan cookie sesi
bank.compada request tersebut meskipun request-nya berasal dari situs lain. -
Server
bank.commelihat cookie valid, menganggap permintaan itu sah, lalu memprosesnya.
Contoh sederhana halaman jahat yang memanfaatkan celah ini kira-kira seperti ini:
<!-- Halaman jahat di situs-penyerang.com -->
<html>
<body onload="document.forms[0].submit()">
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="ke_rekening" value="9999888877" />
<input type="hidden" name="jumlah" value="5000000" />
</form>
</body>
</html>
Perhatikan, tidak ada satu pun baris JavaScript yang mencuri cookie di sini. Penyerang bahkan tidak perlu tahu isi cookie-nya. Mereka cukup memanfaatkan fakta bahwa browser mengirim cookie secara otomatis ke domain yang cocok, siapa pun yang memicu request-nya.
Kenapa Serangan Ini Licik
CSRF beda dengan pencurian kredensial biasa. Korban tidak perlu memasukkan apa pun, tidak ada notifikasi mencurigakan, dan dari sisi tampilan halaman jahat bisa terlihat benar-benar tidak berhubungan dengan situs target. Justru karena "kesenyapannya" inilah CSRF sering luput dari perhatian tim pengembang yang fokusnya lebih ke validasi input atau proteksi terhadap SQL Injection dan XSS.
Getting Started: Menyiapkan Proyek Contoh
Biar teori tadi nggak mengambang, kita bikin server Express kecil-kecilan buat mensimulasikan endpoint transfer dana yang rentan, lalu kita tambal satu per satu.
Step 1: Inisialisasi Proyek
mkdir demo-csrf
cd demo-csrf
npm init -y
npm install express express-session cookie-parser
Kenapa langkah ini penting: express-session kita pakai buat mensimulasikan sesi login, karena CSRF cuma relevan kalau ada sesi yang bisa "dibajak" secara tidak langsung. Tanpa sesi aktif, tidak ada apa pun yang bisa dieksploitasi penyerang.
Step 2: Bikin Server dengan Endpoint yang Rentan
Buat file server.js lalu isi seperti ini dulu, sengaja tanpa proteksi, biar kelihatan masalahnya:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(session({
secret: 'rahasia-belajar',
resave: false,
saveUninitialized: true,
}));
// Simulasi login
app.get('/login', (req, res) => {
req.session.user = 'alama';
res.send('Login berhasil, sesi aktif tersimpan di cookie.');
});
// Endpoint sensitif TANPA proteksi CSRF
app.post('/transfer', (req, res) => {
if (!req.session.user) {
return res.status(401).send('Belum login.');
}
const { ke_rekening, jumlah } = req.body;
res.send(`Transfer Rp${jumlah} ke ${ke_rekening} berhasil diproses.`);
});
app.listen(3000, () => console.log('Server jalan di http://localhost:3000'));
Jalankan dengan:
node server.js
Expected output di terminal:
Server jalan di http://localhost:3000
Kalau Teman-Teman akses http://localhost:3000/login di browser, lalu buka file HTML jahat dari contoh sebelumnya di tab lain (arahkan action ke http://localhost:3000/transfer), request itu akan tetap diproses server karena cookie sesi ikut terkirim. Ini bukti nyata betapa mudahnya endpoint tanpa proteksi dieksploitasi.
Step 3: Menambal dengan CSRF Token (Synchronizer Token Pattern)
![]()
Ini metode paling umum dan paling direkomendasikan. Idenya: setiap sesi punya token acak yang hanya diketahui oleh server dan halaman formulir yang sah. Kalau token yang dikirim tidak cocok, server menolak permintaannya.
npm install csrf
const Tokens = require('csrf');
const tokens = new Tokens();
app.get('/login', (req, res) => {
req.session.user = 'alama';
req.session.csrfSecret = tokens.secretSync();
res.send('Login berhasil, sesi dan secret CSRF tersimpan.');
});
app.get('/form-transfer', (req, res) => {
const csrfToken = tokens.create(req.session.csrfSecret);
res.send(`
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${csrfToken}" />
<input name="ke_rekening" placeholder="Nomor rekening" />
<input name="jumlah" placeholder="Jumlah" />
<button type="submit">Transfer</button>
</form>
`);
});
app.post('/transfer', (req, res) => {
if (!req.session.user) {
return res.status(401).send('Belum login.');
}
const isValid = tokens.verify(req.session.csrfSecret, req.body._csrf);
if (!isValid) {
return res.status(403).send('Token CSRF tidak valid atau hilang.');
}
const { ke_rekening, jumlah } = req.body;
res.send(`Transfer Rp${jumlah} ke ${ke_rekening} berhasil diproses.`);
});
Kenapa cara ini efektif: halaman jahat di domain lain tidak bisa membaca isi token yang dirender di /form-transfer, karena kebijakan same-origin di browser mencegah situs lain mengakses konten halaman dari domain berbeda. Jadi walaupun penyerang bisa memaksa browser mengirim request ke /transfer, mereka tidak akan pernah tahu nilai _csrf yang valid.
Expected output kalau token salah atau tidak dikirim:
403 Forbidden
Token CSRF tidak valid atau hilang.
Kesalahan Umum di Tahap Ini
-
Lupa menyimpan
csrfSecretdi session. Kalau secret-nya dibuat ulang setiap request, token lama otomatis jadi tidak valid, padahal token itu belum kedaluwarsa. -
Mengirim token lewat query string di URL. Token bisa bocor lewat log server atau riwayat browser, jadi sebaiknya selalu dikirim lewat body form atau header khusus.
-
Menggunakan token yang sama untuk semua pengguna. Ini sama saja bohong-bohongan, karena token harus unik per sesi supaya penyerang tidak bisa menebaknya.
Step 4: Menambahkan SameSite Cookie
![]()
Proteksi kedua yang sekarang jadi standar de facto di browser modern adalah atribut SameSite pada cookie. Fitur ini bikin browser menolak mengirim cookie tertentu kalau request-nya berasal dari situs lain.
app.use(session({
secret: 'rahasia-belajar',
resave: false,
saveUninitialized: true,
cookie: {
sameSite: 'lax', // atau 'strict' untuk proteksi lebih ketat
secure: true, // wajib true kalau situs sudah pakai HTTPS
httpOnly: true,
},
}));
Ada tiga nilai yang bisa dipakai, dan masing-masing punya efek berbeda:
Nilai SameSite | Cookie Dikirim Saat Navigasi Lintas Situs? | Cocok Untuk |
|---|---|---|
| Tidak sama sekali, bahkan saat klik link dari situs lain | Aplikasi sensitif seperti internet banking |
| Dikirim untuk navigasi GET biasa (klik link), tapi tidak untuk POST lintas situs | Kebanyakan aplikasi web umum, ini juga default di banyak browser |
| Selalu dikirim, termasuk lintas situs (butuh | Widget atau layanan pihak ketiga yang memang harus lintas domain |
Referensi lengkap soal perilaku ini bisa Teman-Teman cek langsung di dokumentasi MDN Web Docs tentang cookie SameSite, yang selalu diperbarui mengikuti standar browser terbaru.
Kenapa ini bukan pengganti CSRF token, melainkan pelengkap: dukungan SameSite di browser lama belum konsisten, dan ada skenario tertentu (misalnya subdomain yang dianggap "situs sama") yang tetap bisa jadi celah. Jadi idealnya dua lapis ini dipakai bareng, bukan pilih salah satu.
Step 5: Verifikasi Header Origin dan Referer
![]()
Lapisan tambahan yang murah tapi lumayan berguna adalah memeriksa header Origin atau Referer pada request yang mengubah data.
function cekOrigin(req, res, next) {
const origin = req.headers.origin || req.headers.referer;
const domainSendiri = 'http://localhost:3000';
if (!origin || !origin.startsWith(domainSendiri)) {
return res.status(403).send('Request ditolak, sumber tidak dikenali.');
}
next();
}
app.post('/transfer', cekOrigin, (req, res) => {
// logika transfer di sini
});
Perlu jujur soal batasannya: header ini bisa saja tidak ada pada beberapa konfigurasi proxy atau ekstensi browser tertentu, jadi jangan jadikan ini satu-satunya pertahanan. Anggap saja sebagai lapisan tambahan, bukan solusi utama.
Step 6: Menguji Proteksi yang Sudah Dipasang
Setelah semua lapisan terpasang, saatnya menguji apakah proteksinya benar-benar jalan. Coba kirim request tanpa token lewat curl:
curl -X POST http://localhost:3000/transfer \
-d "ke_rekening=9999888877&jumlah=5000000" \
-b "connect.sid=isi-cookie-sesi-di-sini"
Expected output kalau proteksi berfungsi:
403 Forbidden
Token CSRF tidak valid atau hilang.
Kalau responsnya malah 200 OK dan transfer diproses, berarti ada langkah yang terlewat. Cek lagi urutan middleware, karena di Express, urutan pendaftaran middleware itu memengaruhi apakah pengecekan token benar-benar dijalankan sebelum handler utama.
Contoh di Framework Lain: Django
Kalau Teman-Teman kerja di ekosistem Python, kabar baiknya Django sudah menyediakan proteksi CSRF secara bawaan, jadi nggak perlu bikin dari nol.
# views.py
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def transfer_view(request):
if request.method == "POST":
jumlah = request.POST.get("jumlah")
return HttpResponse(f"Transfer sebesar {jumlah} diproses.")
<!-- template.html -->
<form method="post" action="/transfer/">
{% csrf_token %}
<input name="jumlah" />
<button type="submit">Kirim</button>
</form>
Django otomatis menolak request dengan pesan 403 Forbidden (CSRF verification failed) kalau token {% csrf_token %} tidak ada atau tidak cocok. Ini contoh bagus kenapa memilih framework yang sudah punya proteksi bawaan itu menghemat banyak waktu dibanding membangun sendiri dari nol.
Perbandingan Metode Pencegahan CSRF
Supaya lebih gampang menentukan mana yang harus dipakai duluan, berikut ringkasannya:
Metode | Tingkat Efektivitas | Kompleksitas Implementasi | Catatan |
|---|---|---|---|
CSRF Token (Synchronizer Pattern) | Tinggi | Sedang | Jadi standar utama, cocok untuk hampir semua kasus |
SameSite Cookie | Tinggi | Rendah | Gampang dipasang, tapi bergantung dukungan browser |
Double Submit Cookie | Sedang-Tinggi | Rendah | Cocok untuk API stateless, tidak butuh session di server |
Cek Header Origin/Referer | Sedang | Rendah | Bagus sebagai lapisan tambahan, jangan jadi andalan utama |
Verifikasi ulang (OTP/password) | Tinggi | Sedang-Tinggi | Ideal untuk aksi berisiko besar seperti transfer dana |
Rekomendasi berdasarkan kebutuhan:
-
Kalau Teman-Teman membangun aplikasi web tradisional dengan render di server, pakai CSRF token sebagai lapisan utama, ditambah
SameSite=Laxdi cookie session. -
Kalau membangun API yang dikonsumsi SPA (Single Page Application) atau mobile app, double submit cookie biasanya lebih praktis karena tidak bergantung pada session server.
-
Untuk aksi yang risikonya besar seperti transfer dana atau ganti email, tambahkan verifikasi ulang apa pun metode utamanya, karena ini jadi jaring pengaman terakhir.
Kesalahan yang Sering Terjadi dan Cara Mengatasinya
1. Token CSRF Hilang Setelah Refresh atau Buka Tab Baru
Ini biasanya terjadi kalau token digenerate ulang setiap kali halaman dimuat, padahal form-nya sudah terlanjur dirender sebelumnya. Solusinya, pastikan token yang dipakai untuk verifikasi sama dengan yang terakhir dirender ke pengguna, bukan token baru yang dibuat saat submit.
2. Request AJAX Selalu Kena 403
Sering kali developer lupa menyertakan token di header custom saat mengirim request lewat fetch atau axios. Solusinya:
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfTokenDariMeta,
},
body: JSON.stringify({ jumlah: 500000 }),
});
Jangan lupa taruh token di elemen <meta> saat render halaman, supaya JavaScript bisa mengambilnya:
<meta name="csrf-token" content="{{ csrfToken }}" />
3. SameSite=Strict Bikin Fitur Login Lewat Link Email Rusak
Kalau pengguna klik link verifikasi dari email dan tiba-tiba dianggap belum login, kemungkinan besar SameSite=Strict terlalu ketat untuk skenario itu. Solusinya, turunkan ke Lax untuk cookie session utama, dan gunakan Strict khusus untuk cookie yang benar-benar sensitif.
4. Proteksi CSRF Malah Diaktifkan di Endpoint Publik
Endpoint yang memang dirancang untuk diakses lintas situs, seperti webhook dari payment gateway, jangan dipasangi CSRF token, karena pengirimnya bukan browser pengguna. Untuk endpoint semacam ini, verifikasi sebaiknya pakai signature atau API key, bukan token CSRF.
Studi Kasus Singkat: Menambal Endpoint Transfer di Aplikasi Sederhana
Sebagai gambaran nyata, bayangkan sebuah tim kecil yang mengelola aplikasi koperasi simpan pinjam berbasis web. Awalnya endpoint transfer saldo anggota dibangun cepat tanpa proteksi CSRF karena dikejar deadline peluncuran.
Masalah yang ditemukan saat audit keamanan: endpoint /transfer menerima request POST apa pun selama cookie session valid, tanpa validasi asal request. Tim keamanan berhasil mendemonstrasikan proof-of-concept memakai halaman HTML sederhana seperti contoh di awal artikel ini.
Pendekatan yang diambil: tim menambahkan synchronizer token pattern di semua form yang mengubah data, mengatur SameSite=Lax pada cookie session, dan menambahkan verifikasi PIN untuk transfer di atas nominal tertentu.
Hasilnya: setelah proteksi dipasang, proof-of-concept yang sama tidak lagi berhasil, request tanpa token otomatis ditolak dengan status 403. Dari sisi pengguna, hampir tidak ada perubahan pengalaman karena token CSRF berjalan transparan di balik layar.
Pelajaran yang bisa diambil: menunda proteksi CSRF demi mengejar jadwal rilis itu risikonya jauh lebih mahal dibanding waktu yang dibutuhkan untuk memasangnya sejak awal. Menambal belakangan juga lebih ribet karena harus menyisir semua form dan endpoint satu per satu.
Pertanyaan yang Sering Muncul
Apakah HTTPS saja sudah cukup mencegah CSRF? Tidak. HTTPS melindungi data saat transit dari penyadapan, tapi tidak ada hubungannya dengan validasi asal request. Situs ber-HTTPS tetap bisa kena CSRF kalau tidak ada token atau SameSite cookie.
Apakah CAPTCHA bisa menggantikan CSRF token? Bisa membantu untuk aksi tertentu yang jarang dilakukan, tapi tidak praktis dipasang di semua form karena mengganggu pengalaman pengguna. CAPTCHA lebih cocok sebagai lapisan tambahan untuk aksi berisiko tinggi.
Kenapa REST API berbasis token Bearer biasanya lebih aman dari CSRF? Karena token otentikasinya dikirim lewat header Authorization, bukan cookie yang otomatis disertakan browser. Penyerang tidak bisa memaksa browser korban menyisipkan header itu tanpa akses langsung ke storage tempat token disimpan.
Apakah proteksi CSRF perlu dipasang di semua endpoint? Hanya untuk endpoint yang mengubah state, seperti POST, PUT, PATCH, dan DELETE. Endpoint GET yang sifatnya cuma membaca data sebaiknya memang tidak digunakan untuk aksi yang mengubah data, sesuai prinsip desain REST yang direkomendasikan di dokumentasi HTTP method pada MDN.
Menerapkan Proteksi CSRF di Laravel
Kalau tadi kita sudah lihat Django, sekarang giliran ekosistem PHP. Laravel termasuk framework yang paling ramah soal ini, karena proteksi CSRF sudah otomatis aktif begitu Teman-Teman generate project baru. Nggak perlu install package tambahan, nggak perlu bikin middleware dari nol.
Setiap kali Laravel merender form lewat Blade, Teman-Teman cukup tambahkan satu baris:
<form method="POST" action="/transfer">
@csrf
<input type="text" name="ke_rekening" placeholder="Nomor rekening">
<input type="number" name="jumlah" placeholder="Jumlah">
<button type="submit">Transfer</button>
</form>
Directive @csrf itu bakal otomatis dirender jadi hidden input berisi token, mirip banget sama pola yang kita bikin manual di Express tadi. Bedanya, Laravel sudah membungkus semua logika verifikasinya lewat middleware bernama VerifyCsrfToken, yang otomatis jalan di semua route web kecuali yang sengaja dikecualikan.
Kalau Teman-Teman penasaran gimana cara mengecualikan endpoint tertentu (misalnya buat webhook pembayaran yang memang harus diakses lintas domain), tinggal daftarkan path-nya di properti $except:
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'webhook/payment-gateway',
];
Ini penting banget buat dipahami, karena kesalahan yang sering MUGHU temui waktu review kode klien adalah developer yang terlalu semangat mengecualikan banyak route dari proteksi CSRF, padahal alasannya cuma "biar nggak ribet pas testing". Kebiasaan begini gampang kebawa sampai ke production kalau nggak ada review yang ketat.
Untuk request AJAX di Laravel, tokennya bisa diambil dari meta tag yang sudah otomatis disiapkan di layout bawaan:
<meta name="csrf-token" content="{{ csrf_token() }}">
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
Kalau Teman-Teman pakai Laravel Sanctum buat autentikasi SPA, ada mekanisme tambahan namanya CSRF cookie yang diambil lewat endpoint /sanctum/csrf-cookie sebelum request pertama dikirim. Detail lengkapnya bisa Teman-Teman baca langsung di dokumentasi resmi Laravel soal proteksi CSRF, yang menurut MUGHU jadi salah satu dokumentasi paling jelas dibanding framework lain yang pernah MUGHU pelajari.
Expected output kalau token tidak disertakan pada request POST:
419 Page Expired
Status 419 ini khas Laravel, beda dari kebanyakan framework lain yang biasanya balikin 403. Kalau Teman-Teman lihat error ini muncul terus padahal token sudah dipasang, coba cek dulu apakah session driver-nya sudah dikonfigurasi dengan benar di file .env, karena token CSRF di Laravel juga disimpan lewat mekanisme session.
Menerapkan Proteksi CSRF di Spring Boot
Beralih ke ekosistem Java, Spring Security juga sudah menyediakan proteksi CSRF secara bawaan sejak versi lama, dan sebetulnya aktif secara default begitu Teman-Teman menambahkan dependency Spring Security ke proyek.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}
}
Baris CookieCsrfTokenRepository.withHttpOnlyFalse() itu penting kalau Teman-Teman membangun frontend terpisah (misalnya React atau Vue) yang perlu membaca token lewat JavaScript buat dimasukkan ke header request. Kalau dibiarkan default, token disimpan sebagai atribut request biasa yang cuma bisa diakses lewat template server-side seperti Thymeleaf.
Untuk form Thymeleaf, tokennya otomatis disisipkan asal Teman-Teman pakai tag th:action:
<form th:action="@{/transfer}" method="post">
<input type="text" name="keRekening" />
<input type="number" name="jumlah" />
<button type="submit">Transfer</button>
</form>
Spring bakal otomatis menyuntikkan hidden input token CSRF ke form tersebut selama atribut th:action dipakai, bukan atribut action biasa. Ini salah satu jebakan kecil yang sering bikin bingung, karena kalau Teman-Teman pakai action polos, Spring nggak tahu form itu perlu disisipi token, dan hasilnya request bakal ditolak dengan status 403.
Buat request dari sisi JavaScript, ambil token dari cookie XSRF-TOKEN yang otomatis dibuat Spring, lalu kirim lewat header X-XSRF-TOKEN:
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
},
body: JSON.stringify({ jumlah: 500000 }),
});
Pola ini sebetulnya sudah mendekati double submit cookie yang bakal kita bahas lebih detail sebentar lagi, cuma dibungkus rapi oleh Spring supaya developer nggak perlu bikin logikanya dari nol.
Double Submit Cookie: Opsi buat API Tanpa Session Server
Sejauh ini kita banyak membahas synchronizer token pattern, yang mengandalkan penyimpanan token di sisi server lewat session. Tapi gimana kalau arsitektur aplikasi Teman-Teman stateless, misalnya API berbasis JWT yang memang sengaja didesain tanpa session server supaya gampang di-scale secara horizontal?
Di sinilah double submit cookie jadi relevan. Konsepnya cukup sederhana: server mengirim token acak lewat cookie, dan frontend wajib mengirim ulang nilai yang sama itu lewat header custom setiap kali request. Server tinggal membandingkan dua nilai ini, tanpa perlu menyimpan apa pun di database atau session store.
const crypto = require('crypto');
app.get('/init-csrf', (req, res) => {
const csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('csrf_token', csrfToken, {
httpOnly: false, // sengaja false biar bisa dibaca JavaScript
sameSite: 'strict',
secure: true,
});
res.json({ csrfToken });
});
function verifikasiDoubleSubmit(req, res, next) {
const cookieToken = req.cookies.csrf_token;
const headerToken = req.headers['x-csrf-token'];
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
return res.status(403).json({ error: 'CSRF token tidak cocok.' });
}
next();
}
app.post('/api/transfer', verifikasiDoubleSubmit, (req, res) => {
res.json({ status: 'sukses', pesan: 'Transfer diproses.' });
});
Logikanya begini: penyerang di domain lain memang bisa memicu browser korban mengirim cookie csrf_token secara otomatis, tapi mereka nggak bisa membaca isi cookie itu buat dimasukkan ke header X-CSRF-Token, karena kebijakan same-origin browser mencegah JavaScript dari domain berbeda mengakses cookie milik domain lain. Jadi walaupun cookie ikut terkirim, nilai di header tetap kosong atau salah.
Expected output kalau header dan cookie tidak cocok:
{ "error": "CSRF token tidak cocok." }
Kelebihan pola ini jelas: nggak butuh penyimpanan di server, gampang diterapkan di arsitektur microservices yang stateless, dan cocok banget buat API yang dikonsumsi banyak jenis klien sekaligus, baik web, mobile, maupun aplikasi pihak ketiga. Tapi ada catatan yang wajib Teman-Teman ingat: pola ini cuma aman kalau httpOnly di-set false pada cookie tokennya, yang berarti Teman-Teman harus ekstra hati-hati memastikan nggak ada celah XSS di aplikasi, karena kalau ada, penyerang bisa saja mencuri token itu langsung lewat script jahat.
Tools yang Layak Dicoba buat Ngecek Celah CSRF
Ngomongin proteksi tanpa ngomongin cara mengujinya rasanya kurang lengkap. Berikut beberapa tools yang sering MUGHU pakai sendiri waktu melakukan audit keamanan aplikasi, lengkap sama plus minusnya masing-masing.
OWASP ZAP
OWASP ZAP adalah salah satu proxy security scanner gratis yang paling populer buat mengecek kerentanan web, termasuk CSRF. Cara kerjanya, ZAP jadi perantara antara browser dan aplikasi target, lalu menganalisis setiap request yang lewat.
Kelebihan:
-
Gratis dan open source
-
Bisa mendeteksi form tanpa token CSRF secara otomatis lewat fitur passive scan
-
Punya mode automation buat diintegrasikan ke pipeline CI/CD
Kekurangan:
-
Kurva belajarnya lumayan curam buat yang belum terbiasa sama tools security scanning
-
Hasil scan otomatis kadang menghasilkan false positive yang perlu diverifikasi manual
Burp Suite Community Edition
Burp Suite jadi andalan banyak praktisi pentest profesional. Versi Community-nya gratis, meskipun fitur otomatisnya dibatasi dibanding versi Professional yang berbayar.
Kelebihan:
-
Fitur "Repeater" memudahkan Teman-Teman mengulang request manual sambil mengubah nilai token, pas banget buat menguji apakah verifikasi CSRF benar-benar jalan
-
Interface-nya rapi dan mudah dipahami
Kekurangan:
-
Fitur scanning otomatis cuma tersedia di versi berbayar
-
Untuk proyek besar, versi Community terasa terbatas
csurf dan Alternatifnya di Node.js
Package csurf yang sempat populer di ekosistem Express sekarang sudah deprecated dan nggak lagi mendapat update keamanan. MUGHU sengaja nggak pakai package ini di contoh-contoh sebelumnya, dan lebih memilih package csrf yang lebih ringan serta masih aktif dirawat. Kalau Teman-Teman menemukan proyek lama yang masih pakai csurf, sebaiknya segera direncanakan migrasinya.
Helmet.js
Bukan tools khusus CSRF, tapi helmet layak disebut karena membantu mengatur banyak header keamanan sekaligus, termasuk yang berkaitan dengan kebijakan cookie dan konten.
npm install helmet
const helmet = require('helmet');
app.use(helmet());
Kelebihan: instalasi super cepat, langsung menambal banyak celah header sekaligus dengan konfigurasi default yang masuk akal.
Kekurangan: helmet nggak menggantikan proteksi CSRF token, jadi jangan salah kira bahwa pasang helmet saja sudah cukup.
Snyk dan GitHub CodeQL
Buat deteksi otomatis di level kode, Snyk dan GitHub CodeQL bisa membantu menandai pola kode yang rawan, termasuk endpoint sensitif yang lupa dipasangi middleware verifikasi. Keduanya cocok diintegrasikan ke proses code review supaya masalah ketahuan sebelum sempat merge ke branch utama.
Rekomendasi MUGHU pribadi: kalau Teman-Teman baru mulai, cukup pakai OWASP ZAP buat scan awal dan Burp Suite Community buat verifikasi manual. Snyk atau CodeQL lebih pas dipasang belakangan setelah proses development-nya mulai stabil, karena keduanya butuh sedikit effort tambahan buat konfigurasi supaya hasilnya relevan.
Kenapa Web Application Firewall Bukan Solusi Ajaib
Beberapa tim sering berpikir, "Ah, pasang WAF saja beres." Sayangnya nggak sesederhana itu. Web Application Firewall memang bisa membantu memblokir pola request yang mencurigakan, termasuk beberapa percobaan CSRF yang polanya sudah dikenali, tapi WAF bekerja berdasarkan signature dan heuristik, bukan validasi logika bisnis aplikasi Teman-Teman secara spesifik.
Artinya, kalau penyerang bikin request palsu yang formatnya persis sama seperti request sah dari pengguna asli (cuma beda asal domainnya), WAF generik belum tentu bisa membedakan mana yang legitimate dan mana yang bukan. WAF lebih efektif buat memblokir pola serangan yang sudah dikenal luas, seperti SQL injection sederhana atau bot scanning otomatis.
Beberapa layanan WAF komersial seperti Cloudflare atau AWS WAF memang punya rule khusus yang bisa dikustomisasi buat mendeteksi anomali header Origin atau Referer, tapi ini sifatnya melengkapi, bukan menggantikan proteksi di level aplikasi. Anggap saja WAF sebagai satpam di pintu gerbang kompleks, sementara CSRF token adalah kunci pintu rumah Teman-Teman sendiri. Satpam yang galak tetap berguna, tapi rumah Teman-Teman tetap butuh kunci yang benar-benar berfungsi.
CSRF di Dunia SPA dan Mobile App, Masih Relevan Nggak?
Ini pertanyaan yang cukup sering muncul, terutama dari tim yang sudah pindah ke arsitektur SPA (Single Page Application) atau aplikasi mobile native. Jawabannya: relevansinya berubah bentuk, tapi risikonya nggak sepenuhnya hilang.
Kalau aplikasi Teman-Teman menyimpan token autentikasi di localStorage atau sessionStorage, lalu mengirimkannya lewat header Authorization: Bearer, risiko CSRF klasik memang jauh berkurang. Alasannya sederhana: browser nggak otomatis menyertakan isi localStorage ke request lintas domain seperti halnya cookie. Penyerang di domain lain nggak bisa memaksa browser korban menyisipkan header Authorization yang benar, karena mereka nggak punya akses ke storage tempat token itu disimpan.
Tapi ini bukan berarti aman sepenuhnya. Kalau aplikasi Teman-Teman ternyata masih pakai kombinasi cookie session untuk sebagian fitur (misalnya buat "remember me" atau integrasi SSO), celah CSRF tetap bisa muncul di endpoint-endpoint yang mengandalkan cookie tersebut. Ini yang sering luput dari perhatian tim yang terlalu percaya diri karena "sudah pakai token, jadi pasti aman".
Untuk aplikasi mobile native, situasinya sedikit berbeda karena nggak ada konsep browser dan cookie lintas domain seperti di web. Tapi kalau aplikasi mobile Teman-Teman punya komponen WebView yang memuat konten dari domain yang sama dengan aplikasi web utamanya, risiko serupa CSRF tetap bisa muncul lewat celah WebView yang dikonfigurasi longgar.
Rekomendasi praktisnya:
-
Kalau API Teman-Teman murni berbasis token Bearer tanpa cookie sama sekali, prioritas keamanan sebaiknya digeser ke perlindungan terhadap pencurian token, seperti penyimpanan yang aman dan masa berlaku token yang pendek.
-
Kalau masih ada kombinasi cookie dan token, tetap pasang proteksi CSRF di endpoint yang mengandalkan cookie, jangan asumsikan aman cuma karena sebagian fitur sudah pakai token.
-
Untuk WebView di aplikasi mobile, pastikan konfigurasi
allowFileAccessdan origin yang diizinkan dibatasi seketat mungkin.
Cerita dari Lapangan: Audit CSRF di Platform Marketplace Lokal
MUGHU pernah diminta membantu audit keamanan sebuah platform marketplace skala menengah yang melayani transaksi antar penjual dan pembeli di beberapa kota besar. Timnya sudah cukup matang soal validasi input dan proteksi terhadap XSS, tapi ternyata luput satu hal: endpoint buat mengubah rekening tujuan pencairan dana penjual nggak punya proteksi CSRF sama sekali.
Waktu ditelusuri, ternyata endpoint itu dibangun belakangan sebagai fitur tambahan, terpisah dari modul autentikasi utama yang sudah lebih rapi. Karena dikerjakan tim yang berbeda dan dikejar target rilis fitur, proteksi CSRF yang sudah jadi standar di modul lain nggak ikut diterapkan di endpoint baru ini.
Dampak yang bisa terjadi seandainya celah ini dieksploitasi: penyerang bisa mengarahkan korban (penjual yang sedang login) ke halaman jahat yang otomatis mengirim request pengubahan rekening pencairan dana, tanpa sepengetahuan penjual sama sekali. Kalau berhasil, dana hasil penjualan bisa saja tercairkan ke rekening yang sudah diganti diam-diam oleh penyerang.
Langkah yang diambil tim setelah temuan ini:
-
Menambahkan synchronizer token pattern di semua endpoint yang mengubah data sensitif, bukan cuma di modul autentikasi utama.
-
Membuat checklist keamanan wajib yang harus dicentang sebelum fitur baru boleh dirilis, termasuk poin spesifik soal proteksi CSRF.
-
Menambahkan verifikasi lewat OTP yang dikirim ke nomor terdaftar khusus untuk perubahan rekening pencairan dana, sebagai lapisan tambahan di luar token CSRF.
-
Melakukan security review berkala setiap kali ada fitur baru yang menyentuh data finansial.
Pelajaran yang paling berkesan buat MUGHU dari kasus ini adalah soal pentingnya konsistensi standar keamanan lintas tim. Percuma satu modul sudah dipagari rapat kalau modul lain yang dibangun tim berbeda malah kelewatan. Proteksi CSRF idealnya jadi bagian dari template atau boilerplate proyek, bukan sesuatu yang diingat-ingat manual satu per satu setiap kali bikin endpoint baru.
Soal Regulasi: Kenapa CSRF Juga Urusan Kepatuhan di Indonesia
Buat Teman-Teman yang membangun aplikasi finansial atau yang mengelola data pribadi pengguna di Indonesia, CSRF bukan cuma soal teknis, tapi juga menyentuh ranah kepatuhan regulasi.
Undang-Undang Perlindungan Data Pribadi (UU PDP) mewajibkan pengendali data buat menerapkan langkah teknis dan organisasi yang memadai demi melindungi data pribadi dari akses atau perubahan yang tidak sah. Celah CSRF yang memungkinkan perubahan data tanpa persetujuan pengguna jelas bertentangan dengan prinsip ini, karena data bisa berubah tanpa ada tindakan sadar dari pemiliknya.
Buat aplikasi di sektor jasa keuangan, Otoritas Jasa Keuangan (OJK) juga punya ketentuan soal manajemen risiko teknologi informasi yang mengharuskan penyelenggara jasa keuangan digital menerapkan pengamanan sistem elektronik secara berlapis. Kerentanan seperti CSRF yang bisa dieksploitasi buat memindahkan dana tanpa otorisasi jelas termasuk risiko yang harus dimitigasi sesuai semangat ketentuan ini.
Badan Siber dan Sandi Negara (BSSN) juga secara rutin menerbitkan panduan dan imbauan keamanan siber buat penyelenggara sistem elektronik, termasuk soal pentingnya audit keamanan aplikasi secara berkala. Kalau Teman-Teman bekerja di institusi pemerintah atau BUMN, biasanya ada kewajiban tambahan buat melaporkan hasil audit keamanan sistem elektronik ke BSSN secara periodik.
Poin pentingnya, dokumentasi jadi bagian yang nggak kalah krusial dari sisi kepatuhan. Simpan catatan kapan proteksi CSRF diterapkan, hasil pengujian keamanan, dan langkah mitigasi yang diambil kalau ada temuan. Selain berguna buat audit internal, dokumentasi ini juga jadi bukti kalau suatu saat ada pemeriksaan dari regulator atau bahkan proses hukum akibat insiden keamanan.
Checklist Audit CSRF Sebelum Rilis ke Production
Berdasarkan pengalaman menangani beberapa kasus serupa, berikut checklist yang biasa MUGHU pakai sebelum bilang "aman, boleh rilis":
Semua endpoint dengan method POST, PUT, PATCH, dan DELETE sudah dipasangi verifikasi token CSRF atau mekanisme setara
Cookie session sudah diatur dengan atribut
SameSite, minimalLaxCookie session menggunakan atribut
Securekalau aplikasi sudah berjalan di HTTPSToken CSRF unik per sesi, bukan token statis yang sama buat semua pengguna
Endpoint publik seperti webhook sudah dikecualikan dari proteksi CSRF, tapi diverifikasi lewat signature atau API key
Request AJAX dan fetch sudah menyertakan token lewat header, bukan cuma mengandalkan cookie
Sudah ada pengujian manual memakai Burp Suite atau tools sejenis buat memastikan request tanpa token benar-benar ditolak
Aksi berisiko tinggi seperti perubahan rekening atau email sudah punya lapisan verifikasi tambahan seperti OTP
Dokumentasi hasil pengujian keamanan sudah disimpan buat kebutuhan audit
Checklist ini nggak perlu dijalankan cuma sekali di awal proyek. Idealnya, poin-poin ini masuk ke dalam proses review sebelum setiap rilis fitur baru yang menyentuh data sensitif, supaya kejadian seperti studi kasus marketplace tadi nggak terulang.
Beberapa Pertanyaan Tambahan yang Sering Muncul
Apakah proteksi CSRF perlu diterapkan di internal tools yang cuma dipakai tim sendiri? Perlu. Banyak insiden justru terjadi di internal tools yang dianggap "aman karena cuma dipakai orang dalam", padahal karyawan juga bisa membuka email atau link mencurigakan dari luar sambil sesi internal tools-nya masih aktif di tab lain.
Kenapa token CSRF saya selalu berubah tiap kali halaman di-refresh, apakah itu normal? Tergantung implementasinya. Kalau Teman-Teman pakai pola di mana token dibuat ulang setiap request GET ke halaman form, itu wajar, asal server tetap menyimpan referensi token yang valid buat verifikasi. Yang jadi masalah kalau token lama langsung dianggap tidak valid padahal pengguna belum sempat submit form-nya.
Apakah framework backend modern seperti NestJS atau Fastify sudah punya proteksi CSRF bawaan? Sebagian besar menyediakan lewat middleware tambahan, bukan aktif secara default seperti Django atau Laravel. NestJS misalnya perlu package tambahan seperti csrf-csrf, sementara Fastify punya plugin resmi bernama @fastify/csrf-protection. Intinya, selalu cek dokumentasi resmi framework yang Teman-Teman pakai, karena pendekatan defaultnya bisa beda-beda.
Apakah menyimpan token CSRF di localStorage itu ide yang bagus? Sebaiknya dihindari. localStorage rentan diakses lewat celah XSS, dan kalau token CSRF berhasil dicuri lewat XSS, proteksinya jadi percuma. Lebih aman menyimpan token lewat cookie dengan atribut yang tepat, atau langsung merender token ke DOM lewat meta tag seperti contoh Laravel tadi.
Rekomendasi Sesuai Skala Tim dan Anggaran
Buat Teman-Teman yang masih bingung mulai dari mana, berikut gambaran praktis berdasarkan skala tim:
Tim kecil atau proyek personal: Manfaatkan proteksi bawaan framework kalau memang tersedia, seperti Laravel atau Django. Nggak perlu buru-buru investasi ke tools scanning berbayar. Cukup pasang SameSite=Lax, aktifkan CSRF token bawaan, dan sesekali uji manual pakai Burp Suite Community yang gratis.
Tim menengah dengan beberapa produk aktif: Mulai standarkan proteksi CSRF lewat boilerplate atau template proyek internal, supaya nggak ada endpoint baru yang kelewatan seperti studi kasus marketplace tadi. Pertimbangkan integrasi OWASP ZAP ke pipeline CI/CD buat scan otomatis setiap ada perubahan kode.
Perusahaan skala besar atau yang menangani data finansial: Selain proteksi teknis berlapis, pastikan ada proses audit keamanan berkala yang terdokumentasi rapi buat kebutuhan kepatuhan ke OJK atau regulator terkait. Pertimbangkan juga investasi ke tools seperti Snyk atau layanan pentest profesional secara rutin, minimal setahun sekali atau setiap ada perubahan arsitektur besar.
Referensi tambahan yang cukup lengkap dan terus diperbarui soal berbagai pola pencegahan CSRF bisa Teman-Teman cek di OWASP Cheat Sheet Series khusus CSRF Prevention, yang menurut MUGHU jadi salah satu rujukan paling lengkap kalau Teman-Teman butuh detail teknis lebih dalam di luar yang sudah dibahas di artikel ini.
Satu hal yang selalu MUGHU tekankan ke tim mana pun yang pernah diajak kerja sama: proteksi CSRF itu murah dari segi effort implementasi dibanding potensi kerugian kalau sampai dieksploitasi. Beberapa baris kode dan satu middleware tambahan jauh lebih ringan ketimbang harus menjelaskan ke pengguna kenapa dana mereka berpindah tanpa persetujuan, atau harus berurusan dengan regulator gara-gara insiden yang sebenarnya bisa dicegah sejak awal.
Proteksi CSRF di WordPress: Nonce yang Sering Disepelekan
Buat Teman-Teman yang mengelola website WordPress, entah itu toko online kecil, company profile, atau blog personal, proteksi CSRF sebenarnya sudah disediakan lewat sistem nonce. Sayangnya istilah "nonce" ini bikin banyak orang mengira itu cuma soal enkripsi, padahal fungsinya persis sama seperti CSRF token yang sudah kita bahas dari awal.
Setiap kali Teman-Teman bikin form di plugin atau tema custom, WordPress punya fungsi bawaan buat generate dan verifikasi nonce:
<?php wp_nonce_field('update_profil_toko', 'nonce_field'); ?>
Lalu pas menerima submission-nya, wajib diverifikasi dulu:
if (!wp_verify_nonce($_POST['nonce_field'], 'update_profil_toko')) {
wp_die('Permintaan tidak valid atau kedaluwarsa.');
}
Bedanya sama synchronizer token pattern yang kita bikin manual tadi cuma di penamaan. Nonce WordPress juga punya masa kedaluwarsa, biasanya sekitar 24 jam, jadi kalau ada pengguna yang buka form terus dibiarkan lama tanpa disubmit, nonce-nya bisa basi dan perlu direfresh.
MUGHU beberapa kali nemuin kasus di proyek klien yang pakai WordPress, plugin custom-nya bikin sendiri endpoint lewat admin-ajax.php tapi lupa pasang verifikasi nonce sama sekali. Ini pintu masuk yang gampang dieksploitasi, apalagi kalau endpoint itu bisa mengubah data penting kayak rekening penerima pembayaran di plugin toko online custom. Kalau Teman-Teman bikin plugin sendiri, cek dulu panduan resmi soal nonce di developer.wordpress.org, karena penjelasannya lengkap dan terus mengikuti versi WordPress terbaru.
Next.js dan Framework SSR Modern, Jangan Kira Otomatis Aman
Ini bagian yang menurut MUGHU penting buat Teman-Teman yang kerja di ekosistem JavaScript modern kayak Next.js, Nuxt, atau SvelteKit. Beda sama Django atau Laravel yang proteksi CSRF-nya aktif dari awal, kebanyakan framework SSR berbasis JavaScript ini nggak menyediakan proteksi CSRF otomatis buat endpoint API biasa.
Pengecualian menarik ada di Next.js App Router lewat fitur Server Actions. Sejak versi 13.4 ke atas, Next.js otomatis memverifikasi header Origin setiap kali Server Action dipanggil, jadi request dari domain lain bakal ditolak sebelum sempat masuk ke logika bisnis. Tapi ini cuma berlaku buat Server Actions, bukan buat Route Handler biasa di folder app/api.
Jadi kalau Teman-Teman masih pakai Route Handler konvensional buat endpoint yang mengubah data, proteksinya tetap harus dibikin manual, mirip pola yang kita pakai di Express dari awal:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (request.method === 'POST') {
const origin = request.headers.get('origin');
const host = request.headers.get('host');
if (!origin || new URL(origin).host !== host) {
return NextResponse.json(
{ error: 'Origin tidak valid.' },
{ status: 403 }
);
}
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};
Detail lengkapnya bisa Teman-Teman baca di dokumentasi resmi Next.js soal Server Actions, yang menjelaskan cukup gamblang kenapa validasi Origin ini penting buat mencegah mutation dari domain lain.
Supaya gambarannya lebih jelas, begini kira-kira kondisi default beberapa framework SSR populer:
-
Next.js Server Actions sudah punya verifikasi Origin otomatis, nggak perlu konfigurasi tambahan.
-
Next.js Route Handler biasa sama sekali nggak punya proteksi bawaan, wajib dibikin manual.
-
Nuxt 3 juga nggak punya proteksi default, biasanya ditambal lewat modul komunitas seperti
nuxt-csurf. -
SvelteKit punya validasi Origin dasar khusus buat form actions, tapi endpoint API custom tetap perlu penanganan sendiri.
-
Remix sama seperti Next.js Route Handler, proteksinya harus dibangun dari nol.
Poin yang perlu Teman-Teman garis bawahi, jangan asumsikan framework modern otomatis aman cuma karena namanya kedengaran canggih. Selalu cek dokumentasi resmi masing-masing dan uji manual pakai cara yang sudah kita bahas di bagian Burp Suite tadi.
GraphQL dan CSRF: Satu Pintu, Risikonya Nggak Kalah Besar
Buat Teman-Teman yang kerja di proyek berbasis GraphQL, ada kesalahpahaman yang lumayan sering muncul, "GraphQL kan cuma satu endpoint POST, jadi CSRF-nya otomatis kebentur CORS." Anggapan ini setengah benar, setengah berbahaya kalau ditelan mentah-mentah.
Kalau server GraphQL Teman-Teman menerima request dengan Content-Type application/x-www-form-urlencoded atau text/plain, form HTML sederhana tetap bisa memicu request itu tanpa perlu preflight CORS, persis kayak endpoint REST biasa yang sudah kita bahas. Celahnya muncul kalau autentikasi masih mengandalkan cookie session, sementara body query GraphQL-nya dikirim lewat form tersembunyi.
Cara paling praktis buat menutup celah ini:
-
Wajibkan Content-Type
application/jsonbuat semua request ke endpoint GraphQL, lalu tolak request dengan Content-Type lain. Form HTML biasa nggak bisa mengirim body JSON murni tanpa JavaScript, dan kalau pakai JavaScript dari domain lain, browser bakal memicu preflight CORS yang otomatis diblokir kalau konfigurasi CORS-nya benar. -
Tambahkan header custom wajib, misalnya
X-Requested-WithatauApollo-Require-Preflight, yang cuma bisa disisipkan lewat JavaScript dan otomatis memaksa browser melakukan preflight request. -
Tetap terapkan SameSite cookie dan validasi Origin sebagai lapisan tambahan, sama seperti REST API biasa.
Apollo Server, misalnya, punya opsi bawaan buat mewajibkan header preflight ini, jadi Teman-Teman nggak perlu bikin logikanya dari nol kalau memang pakai Apollo sebagai implementasi GraphQL-nya.
csrf-sync: Opsi Lain Selain Package csrf
Di bagian tools sebelumnya, MUGHU sempat singgung soal csurf yang sudah deprecated. Selain package csrf yang kita pakai buat contoh di Express tadi, ada satu lagi yang belakangan makin sering direkomendasikan komunitas, namanya csrf-sync.
npm install csrf-sync
const { csrfSync } = require('csrf-sync');
const {
generateToken,
csrfSynchronisedProtection,
} = csrfSync({
getSessionIdentifier: (req) => req.session.id,
});
app.get('/form-transfer', (req, res) => {
const csrfToken = generateToken(req);
res.send(`<input type="hidden" name="_csrf" value="${csrfToken}" />`);
});
app.post('/transfer', csrfSynchronisedProtection, (req, res) => {
res.send('Transfer diproses.');
});
Kelebihan dibanding package csrf biasa, csrf-sync sudah membungkus middleware verifikasinya jadi satu baris, jadi Teman-Teman nggak perlu manual bikin fungsi pengecekan seperti contoh di awal artikel. Kekurangannya, dokumentasinya belum sekomplet package yang lebih senior, jadi kalau butuh kustomisasi lebih dalam, mungkin perlu baca langsung source code-nya di GitHub.
Pilihan antara csrf dan csrf-sync sebenarnya soal preferensi. Kalau Teman-Teman suka kontrol penuh atas setiap langkah verifikasi, package csrf lebih cocok. Kalau lebih suka setup cepat dengan konvensi yang sudah ditentukan, csrf-sync bisa jadi pilihan yang menghemat waktu.
Menulis Automated Test Biar Proteksi CSRF Nggak Regresi Diam-Diam
Salah satu hal yang sering kelewat, proteksi CSRF yang sudah dipasang bisa saja rusak diam-diam gara-gara refactor kode, tanpa ada yang sadar sampai insiden beneran terjadi. Solusinya, tulis automated test yang khusus memverifikasi perilaku ini, jangan cuma diuji manual sekali waktu development.
npm install --save-dev jest supertest
const request = require('supertest');
const app = require('../server');
describe('Proteksi CSRF pada endpoint /transfer', () => {
it('menolak request tanpa token CSRF', async () => {
const agent = request.agent(app);
await agent.get('/login');
const response = await agent
.post('/transfer')
.send({ ke_rekening: '9999888877', jumlah: '5000000' });
expect(response.status).toBe(403);
});
it('menerima request dengan token CSRF yang valid', async () => {
const agent = request.agent(app);
await agent.get('/login');
const formPage = await agent.get('/form-transfer');
const tokenMatch = formPage.text.match(/name="_csrf" value="([^"]+)"/);
const csrfToken = tokenMatch[1];
const response = await agent
.post('/transfer')
.send({ ke_rekening: '9999888877', jumlah: '5000000', _csrf: csrfToken });
expect(response.status).toBe(200);
});
});
Expected output kalau kedua test ini dijalankan dengan npx jest:
PASS __tests__/csrf.test.js
Proteksi CSRF pada endpoint /transfer
✓ menolak request tanpa token CSRF
✓ menerima request dengan token CSRF yang valid
Test semacam ini enak dimasukkan ke pipeline CI, biar setiap kali ada pull request yang menyentuh middleware autentikasi atau session, ada jaring pengaman otomatis yang langsung teriak kalau proteksinya ternyata kebobol gara-gara perubahan kode.
Kapan Proteksi CSRF Boleh Sedikit Dilonggarkan?
Pertanyaan ini sering muncul dari tim yang merasa proteksi CSRF bikin proses development jadi lebih ribet, terutama pas testing lokal. Jawaban jujurnya, ada beberapa skenario spesifik di mana pelonggaran wajar dilakukan, asal nggak kebawa sampai production.
Di environment development lokal, banyak tim mematikan sementara pengecekan Origin karena sering testing pakai Postman atau curl yang nggak mengirim header Origin sama sekali. Ini sah-sah saja, asal ada flag environment yang jelas:
if (process.env.NODE_ENV !== 'production') {
// skip verifikasi origin buat kebutuhan testing lokal
} else {
app.use(cekOrigin);
}
Yang jadi masalah kalau flag ini kelupaan dan proteksinya beneran nggak aktif pas sudah live. Makanya, checklist deployment yang sudah dibahas sebelumnya wajib mengecek variabel environment ini sebelum rilis.
Skenario lain, endpoint yang memang didesain publik dan stateless, seperti API yang cuma menerima API key tanpa cookie session sama sekali. Kalau autentikasinya murni lewat header Authorization atau API key custom, proteksi CSRF konvensional memang nggak relevan lagi, karena browser nggak otomatis menyisipkan credential jenis ini ke request lintas domain. Yang tetap wajib dijaga di skenario ini justru soal masa berlaku API key dan pembatasan scope aksesnya, bukan soal token CSRF.
Kesimpulan
CSRF bukan sekadar istilah teknis yang cuma dibahas di dokumentasi keamanan, tapi celah nyata yang bisa dieksploitasi kalau session cookie dan validasi origin tidak ditangani dengan benar. Dari pembahasan token _csrf yang wajib divalidasi di setiap request mengubah state, sampai pengecekan header Origin sebagai lapisan pertahanan tambahan, semuanya mengarah ke satu prinsip yang sama: jangan pernah percaya begitu saja pada request yang datang bersama cookie session, sekalipun terlihat sah secara teknis. Automated test yang sudah dicontohkan di atas juga bukan sekadar formalitas, melainkan jaring pengaman yang memastikan proteksi ini tidak diam-diam hilang setiap kali ada perubahan kode di middleware autentikasi.
Yang membedakan implementasi CSRF yang solid dengan yang rapuh biasanya bukan soal seberapa canggih library yang dipakai, melainkan seberapa disiplin tim menjaga konsistensi antara environment development dan production. Melonggarkan pengecekan Origin saat testing lokal itu wajar, tapi kalau flag environment-nya lupa dicabut sebelum deploy, proteksi yang sudah dibangun susah payah jadi sia-sia. Begitu juga dengan endpoint stateless berbasis API key, di mana fokus keamanannya bergeser dari token CSRF ke soal rotasi dan pembatasan scope akses.
Pada akhirnya, keamanan aplikasi web itu soal kebiasaan, bukan sekali pasang lalu dilupakan. Kalau proteksi CSRF di aplikasimu masih mengandalkan asumsi "kayaknya aman" tanpa pernah diuji secara otomatis, sekaranglah waktu yang tepat untuk mulai menulis test-nya dan memasukkan checklist environment ke pipeline CI. Langkah kecil ini jauh lebih murah dibanding menanggung akibat transaksi ilegal yang lolos gara-gara satu baris kode yang lupa dijaga.
Referensi
CodingStudio. (2026). CSRF Adalah: Pengertian, Jenis, dan Cara Mencegahnya.
Codepolitan. (2026). CSRF (Cross Site Request Forgery): Pengertian, Jenis, dan Cara Mencegahnya.
Dewaweb. (2026). CSRF (Cross Site Request Forgery): Pengertian dan Cara Mencegahnya.
DomaiNesia. (2026). Kenali Apa Itu CSRF dan Bagaimana Cara Mencegahnya.
Dibimbing. (2026). CSRF Adalah: Pengertian, Cara Kerja, dan Pencegahannya.
Clouden. (2026). Apa Itu CSRF? Pengertian, Cara Kerja, dan Cara Mencegah Serangan.
Hosteko. (2026). CSRF (Cross Site Request Forgery): Pengertian, Cara Kerja, dan Cara Mencegahnya.
ITBox. (2026). Kenali Serangan Cross-Site Request Forgery (CSRF) dan Cara Mencegahnya.
Hostragons. (2026). Serangan CSRF (Cross-Site Request Forgery) dan Teknik Pertahanan.
Bfotool. (2026). Apa Itu CSRF dan Bagaimana Cara Mencegahnya.
Komentar (0)
Belum ada komentar. Jadilah yang pertama berbagi pendapat!
Tinggalkan komentar