Mengamankan aplikasi fullstack dengan lensa OWASP: menutup XSS dan SQL injection, memahami proteksi CSRF pada server actions, menerapkan Content Security Policy, dan mengelola secrets dengan aman.

Keamanan bukan fitur yang "ditambahkan terakhir" — ia adalah keputusan desain yang merembes ke setiap lapisan. Episode ini membahas security fullstack dengan kerangka OWASP Top 10 sebagai peta: XSS, CSRF, injection, dan pengelolaan secrets. Kalian tidak perlu menjadi pentester; kalian perlu memahami pola serangan agar tidak membangun celah.
Mengapa topik ini khusus fullstack? Karena celah keamanan lahir di persimpangan lapisan: input dari browser masuk ke server, query menggabungkan string, atau file yang diunggah disajikan sebagai HTML. Memahami kedua sisi adalah senjata terbaik kalian.
Lima dari sepuluh kerentanan paling umum yang paling relevan untuk aplikasi fullstack:
| Peringkat | Kerentanan | Inti Masalah |
|---|---|---|
| A01 | Broken Access Control | User bisa akses data yang bukan miliknya |
| A02 | Cryptographic Failures | Data sensitif tidak dienkripsi |
| A03 | Injection | Input user dieksekusi sebagai kode |
| A05 | Security Misconfiguration | Default yang tidak aman |
| A07 | XSS | Script jahat dieksekusi di browser korban |
Fokus episode ini: Injection (A03) dan XSS (A07) — keduanya paling sering muncul dan paling bisa dicegah dengan praktik rutin.
XSS terjadi saat input user dirender sebagai kode. Tiga jenisnya:
| Jenis | Cara Kerja |
|---|---|
| Reflected | Payload di URL, dirender di response |
| Stored | Payload tersimpan di database, dirender untuk semua |
| DOM | Payload dijalankan oleh JavaScript client |
React/Next.js secara default melakukan escaping — teks yang kalian render sebagai string tidak akan dieksekusi:
const userInput = "<script>alert(1)</script>"
return <p>{userInput}</p> // aman: dirender sebagai teksCelah muncul saat kalian sengaja mematikan proteksi. Dua pelanggaran paling umum:
dangerouslySetInnerHTML — render HTML mentah. Hindari; jika terpaksa, sanitasi dulu dengan library (DOMPurify).// JANGAN — HTML mentah dari user
<div dangerouslySetInnerHTML={{ __html: userContent }} />Important
Aturan XSS yang paling mudah diingat: semua input tidak dipercaya, semua output di-escape. Saat harus me-render HTML buatan user (konten Markdown, rich text), jalankan melalui sanitizer yang matang (DOMPurify di client, sanitize-html di server) — dan pertimbangkan bahwa "membersihkan HTML" lebih sulit dari kelihatannya.
Injection klasik: string input digabung langsung ke query. Contoh buruk:
// JANGAN — concatenation SQL mentah
const sql = `SELECT * FROM users WHERE email = '${email}'`Untungnya Prisma selalu memakai parameterized query — input tidak pernah dicampur langsung ke SQL. Dengan ORM, risiko ini hampir hilang otomatis. Yang tersisa: query mentah ($queryRaw). Jika wajib memakainya, jangan gabungkan variabel:
const rows = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${email}
`Template literal Prisma menangani binding dengan aman — tidak seperti menggabungkan string manual.
CSRF: penyerang menipu browser korban (yang sudah login) untuk mengirim request jahat. next-auth dan server actions Next.js menangani ini dengan double-submit token secara bawaan — form server action sudah terlindungi tanpa kerja ekstra.
Masalah muncul jika kalian membangun API publik (episode 5) yang mengubah data dan dipanggil dari browser dengan cookie session. Untuk itu, periksa Origin/Referer pada request mutasi:
export async function POST(request: Request) {
const origin = request.headers.get("origin")
if (origin !== process.env.APP_URL) {
return Response.json({ error: "Forbidden" }, { status: 403 })
}
// proses mutasi...
}CSP adalah aturan yang memberi tahu browser sumber apa saja yang boleh dimuat. Ini lapisan pertahanan kedua saat XSS lolos — script dari domain tak dikenal diblokir:
export default function NextConfig() {
return {
headers: async () => [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self'",
].join("; "),
},
],
},
],
}
}Mulai ketat lalu longgarkan seperlunya (karena Next.js butuh 'unsafe-inline' untuk style, dan inline script tertentu). Log pelanggaran CSP di awal — jangan langsung blokir.
Sebagian besar "kebocoran API key" adalah kesalahan sederhana yang bisa dicegah:
.env* (sudah di .gitignore repo ini).NEXT_PUBLIC_ untuk secret server — prefiks itu mengirim nilai ke browser!# .env.local (hanya server)
DATABASE_URL="postgresql://..."
STORAGE_SECRET_KEY="rahasia-server"
# NEXT_PUBLIC_ hanya untuk nilai yang BOLEH dilihat browser
NEXT_PUBLIC_APP_URL="https://app.example.com"Warning
Aturan paling murah di dunia keamanan: apa pun yang diawali NEXT_PUBLIC_ akan dikirim ke browser. Jika suatu secret terlihat di DevTools, ia sudah tidak bisa dianggap rahasia. Audit rutin: grep NEXT_PUBLIC_ di repo kalian dan pastikan tidak ada yang sensitif.
Jalankan checklist berikut di aplikasi catatan:
$queryRaw — apakah ada variabel tercampur ke string SQL?dangerouslySetInnerHTML — hapus atau sanitasi.Origin.NEXT_PUBLIC_ — jangan ada secret di dalamnya.rg "dangerouslySetInnerHTML|queryRaw|NEXT_PUBLIC_.*KEY" src/Inti yang harus dibawa pulang:
$queryRaw.Origin.NEXT_PUBLIC_.Di episode 17 selanjutnya kita membahas deployment & CI/CD — environments, pipeline, dan deploy produksi — agar aplikasi yang sudah kalian bangun bisa sampai ke tangan pengguna dengan aman. Sampai jumpa di episode 17!