2026-06-21 · 7 min
CSRF di SPA 2026: Masih Relevan?
Bulan lalu saya security review SaaS fintech klien Jakarta (regulator OJK, butuh annual security audit). CSRF dipertanyakan: “SameSite cookie sudah default Lax di browser modern, masih perlu token CSRF?” Saya audit 4 app klien saya yang ada paying customer + Indonesian financial context. Hasilnya bikin saya tetap implement CSRF protection.
Konteks teknikal
CSRF (Cross-Site Request Forgery) attack: penyerang trick user untuk submit request ke site target dengan cookie auth user. Cookie auto-attach by browser → server treat sebagai legitimate.
Defense lama:
- SameSite cookie: Strict / Lax / None. Lax sekarang default di Chrome, Firefox, Safari.
- CSRF token: per-session token yang harus included di POST/PUT/DELETE.
- Origin/Referer header check: server validate Origin.
Dengan SameSite=Lax default: cookie tidak dikirim di cross-site request kecuali top-level navigation GET. Apakah ini sudah cukup?
Audit 4 app klien
Saya pakai pendekatan: identify attack surface, lalu test exploit.
App 1: SaaS HR
- Auth: Auth.js session cookie, SameSite=Lax (default)
- CSRF token: tidak ada
- Origin check: tidak ada
Saya coba: buat malicious page dengan auto-submit form POST ke /api/employees/delete. Browser kirim cookie (top-level POST? Tidak, hanya GET top-level. Form POST di iframe atau cross-site fetch: tidak kirim cookie).
Result: SameSite=Lax cukup proteksi standard form attack.
Tapi: saya temukan endpoint GET yang trigger destructive action (legacy code dari pre-2024):
GET /api/admin/disable-user?id=123
GET destructive = exploitable via <img src="..."> di malicious site. User klik link → auto-execute.
Risk: HIGH. Saya report ke klien, fix dengan migrate ke POST + audit semua endpoint untuk GET destructive.
App 2: Fintech (payment processing)
- Auth: JWT di httpOnly cookie + Refresh token rotation
- CSRF token: implemented (double-submit)
- Origin check: yes, strict whitelist
Test:
- Form submit cross-origin → blocked (CSRF token mismatch + Origin reject)
- XHR cross-origin → blocked (CORS preflight reject)
- Subdomain attack: saya simulasi
attacker.fintech-client.com(hypothetical takeover via wildcard DNS). Origin domain match SameSite policy (same-site karena same eTLD+1). Cookie kirim.
Subdomain takeover di Indonesia rare untuk corporate. Tapi audit found wildcard cert + DNS misconfiguration yang theoretically allow this. Saya rekomendasi tighten.
App 2 setup OK overall.
App 3: SaaS invoice
- Auth: Session cookie SameSite=Lax
- CSRF token: tidak ada
- Server: Next.js Server Actions (which has built-in CSRF protection via Next-Action header)
Wait, Next.js Server Actions yang saya pakai claim CSRF protection automatic. Bagaimana?
Saya audit source: Server Actions verify Next-Action header dan Origin match. Origin reject for cross-site request. Implicit CSRF protection via Origin check + custom header (which CORS preflight enforce).
Test exploit:
- Cross-site form submit dengan target Server Action: rejected karena tidak ada
Next-Actionheader - Cross-site fetch with custom header: CORS preflight fail
- Top-level navigation: SameSite=Lax block cookie
Verdict: Server Actions effectively immune ke standard CSRF. Tapi: kalau saya tambah API route REST yang share session cookie, exposure muncul lagi.
Untuk klien ini, app pure Server Actions: OK. Saya audit untuk memastikan tidak ada hybrid REST endpoint yang lupa CSRF.
App 4: POS warung (legacy)
- Auth: session cookie, SameSite tidak set (default browser = Lax untuk Chrome 80+, tapi some browser pengguna pakai Samsung Internet versi lama)
- CSRF token: tidak ada
- Origin check: tidak ada
Browser fingerprint klien (analytics Plausible):
- Chrome mobile: 68%
- Samsung Internet: 14% (versi distribusi: 12% latest, 2% Samsung Internet 14 / Android 9 yang masih default ke SameSite=None)
- Safari iOS: 11%
- UC Browser: 4% (default SameSite=None di versi lama)
- Lain: 3%
6% user app potensi vulnerable karena browser tidak default ke SameSite=Lax. Untuk 8400 user app: ~504 user.
Saya rekomendasi:
- Set
SameSite=Laxexplicit di cookie config (don’t rely on browser default) - Implement double-submit CSRF token untuk POST endpoint
- Origin check untuk semua mutation endpoint
Implementation effort: 4 jam. Klien setuju.
Pattern 2026 yang saya pakai
Untuk app baru, pattern minimum:
// Set cookie explicit
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax; Path=/
// Middleware check
function csrfMiddleware(req) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
// 1. Origin/Sec-Fetch-Site check
const origin = req.headers.get('Origin') || req.headers.get('Referer');
if (!origin || !isAllowedOrigin(origin)) {
return new Response('Forbidden', { status: 403 });
}
// 2. Double-submit CSRF token (untuk REST endpoint)
const cookieToken = getCookie(req, 'csrf-token');
const headerToken = req.headers.get('X-CSRF-Token');
if (!cookieToken || cookieToken !== headerToken) {
return new Response('Forbidden', { status: 403 });
}
}
}
Plus untuk action critical (transfer dana, password change, account delete):
- Explicit re-authentication step (re-enter password atau OTP)
Defense in depth.
Yang break
-
Mobile app: Flutter / React Native client tidak punya browser CORS protection. CSRF token + Origin check tetap matter (Flutter manual attach token).
-
Webhook receiver: endpoint POST yang trusted by external service (Midtrans webhook, WA Business webhook). Tidak boleh require CSRF token. Saya separate route prefix
/api/webhook/*dengan signature verification HMAC sebagai gantinya. -
CSRF token rotation: kalau token bocor di logs / XSS, harus rotate. Saya tie token rotation ke session refresh.
-
SPA + non-SPA route mix: Next.js app dengan page yang punya
<form>non-JS submit (untuk progressive enhancement) — Server Action pattern handle ini OK. REST endpoint butuh manual CSRF token injection ke form.
Cost (engineering)
Untuk app baru: implementasi CSRF protection 4-8 jam. Bukan trivial, tapi tidak prohibitive.
Untuk audit + fix existing app: 1-3 hari per app, depending size.
Klien fintech bayar saya Rp 24jt untuk security audit + fix. Worth-it untuk peace of mind sebelum OJK audit.
Verdict
CSRF di 2026 masih relevan:
- SameSite=Lax default helpful tapi tidak 100% (browser distribution Indonesia tidak monolitik)
- GET destructive endpoint masih exist di legacy code
- Subdomain takeover bukan paranoid scenario untuk perusahaan growing
- Mobile native client tidak dapat browser protection
Defense-in-depth: SameSite + CSRF token + Origin check + re-auth untuk critical action.
Bukan magic — pattern lama tetap valid. Browser security feature additive, bukan replacement.
Pattern saya stick: implement CSRF protection di semua app dengan paying customer. Audit semester sekali. Untuk side project tanpa real consequences: SameSite default OK, tapi pattern muscle memory bagus untuk dipertahankan.
Untuk konteks browser dist Indonesia: jangan asumsi user pakai Chrome latest. Samsung Internet + UC Browser masih signifikan di SMB demographic.
Ditulis oleh Reza Pradipta