Belajar ExpressJS - Security Best Practices
Episode 18 of 28

Belajar ExpressJS - Security Best Practices

Mengeraskan aplikasi Express melawan serangan umum: Helmet dan secure headers, konfigurasi CORS yang benar, SQLi dan NoSQLi, XSS, CSRF, serta checklist hardening produksi.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Episode 12-17 telah menumpuk lapisan pertahanan: hashing, validasi, config, dan logging. Episode 18 merangkai semuanya menjadi strategi hardening yang menutup kategori serangan web paling umum — dan menambahkan yang belum kita bahas: secure headers, CORS, XSS, dan CSRF.

Mengapa ini episode tersendiri? Karena keamanan bukan satu fitur, melainkan kombinasi banyak kontrol yang saling melengkapi. Validasi input (episode 13) menahan NoSQLi, parameterized query (episode 10) menahan SQLi, escaping template (episode 11) menahan XSS. Episode ini menutup sisa celah dan mengikat semuanya jadi satu lapisan pertahanan.

Secure Headers dengan Helmet

Memasang Helmet

Browser menghormati sejumlah header keamanan yang mencegah eksploitasi sisi client. Helmet memasangnya sekaligus:

Install helmet
npm install helmet
JSPasang Helmet di app.js
import helmet from "helmet"
 
app.use(helmet())

Satu baris ini memasang header antara lain:

HeaderFungsi
Content-Security-PolicyMembatasi sumber script/style yang boleh dimuat (anti-XSS)
Strict-Transport-SecurityMemaksa HTTPS
X-Content-Type-OptionsMencegah MIME sniffing
X-Frame-OptionsMencegah clickjacking
Referrer-PolicyMengontrol info yang dikirim ke situs lain

Menyesuaikan CSP

Untuk aplikasi yang memuat script dari sumber tertentu, sesuaikan CSP:

JSCSP dengan allowlist sumber
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", "data:"],
      },
    },
  })
)

CSP yang terlalu ketat bisa memblokir fungsi situs; yang terlalu longgar tidak berguna. Mulai ketat, lalu tambahkan sumber yang benar-benar dibutuhkan — bukan sebaliknya.

Konfigurasi CORS yang Benar

Masalah Default cors()

app.use(cors()) (episode 5) mengizinkan semua origin — memudahkan development, berbahaya di produksi. Konfigurasi produksi membatasi ke daftar origin:

JSCORS dengan allowlist
import cors from "cors"
import { config } from "./config.js"
 
app.use(
  cors({
    origin: config.corsOrigins,
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    credentials: true,
    maxAge: 86400,
  })
)
  • origin diambil dari config.corsOrigins (episode 16) — satu sumber kebenaran.
  • credentials: true diperlukan jika memakai cookie (episode 12) — dan saat credentials aktif, origin tidak boleh *.
  • maxAge menyimpan hasil preflight sehingga request CORS berikutnya lebih cepat.

Important

CORS adalah kontrol browser, bukan keamanan server: ia mencegah browser lain membaca respons, tapi tidak menghentikan curl atau server-to-server. CORS menahan klien web tidak sah, bukan API abuse — rate limiting (episode 20) yang menangani yang terakhir.

SQLi dan NoSQLi

SQL Injection

SQL injection terjadi saat input disisipkan mentah ke string SQL. Parameterized query (episode 10) menutupnya — jangan pernah interpolasi:

JSSalah vs benar untuk SQL
// SALAH: rentan SQL injection
const { rows } = await pool.query(`SELECT * FROM users WHERE email = '${email}'`)
 
// BENAR: parameterized
const { rows } = await pool.query(
  "SELECT * FROM users WHERE email = $1",
  [email]
)

NoSQL Injection

MongoDB rentan versi JSON-nya: operator seperti $ne bisa dipakai untuk memanipulasi query jika input tidak dibersihkan:

JSContoh NoSQL injection
// Payload berbahaya: { "email": { "$ne": null }, "password": { "$ne": null } }
const user = await User.findOne(req.body)
 
// Mengapa berbahaya: kondisi $ne bernilai true untuk hampir semua dokumen

Pertahanan paling efektif adalah validasi + sanitasi (episode 13) — schema zod menolak bentuk objek seperti { "$ne": null } sebelum sampai ke query. Validasi bukan pelengkap keamanan; ia adalah garis pertahanan utama.

XSS dan CSRF

XSS (Cross-Site Scripting)

XSS terjadi saat input pengguna dirender tanpa di-escape. Lapisan pertahanannya bertumpuk:

  • Escaping output di template (<%= %>, episode 11) — menetralkan script saat render.
  • CSP (Helmet) — membatasi script yang boleh jalan meski markup ter-inject.
  • Cookie HttpOnly (episode 12) — token sesi tidak bisa dibaca JavaScript yang ter-inject.
  • Sanitasi (episode 13) — memotong tag <script> dari input.
JSSanitasi input di schema
export const commentSchema = z.object({
  body: z
    .string()
    .trim()
    .max(2000)
    .transform((v) => v.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "")),
})

CSRF (Cross-Site Request Forgery)

CSRF menipu browser pengguna yang sudah login untuk mengirim request berbahaya — memakai cookie sesi yang otomatis ikut terkirim. Pertahanan utamanya:

  • SameSite cookie: lax atau strict (episode 12) memblokir pengiriman cookie lintas situs — menyelesaikan mayoritas kasus.
  • CSRF token: untuk kasus yang butuh SameSite=None, kirim token acak yang harus disertakan request. Untuk API berbasis JWT di header, CSRF praktis tidak relevan — header tidak otomatis terkirim lintas situs.
JSCookie SameSite untuk anti-CSRF
app.use(
  session({
    secret: config.sessionSecret,
    cookie: {
      httpOnly: true,
      sameSite: "lax",
      secure: config.isProduction,
    },
  })
)

Warning

Jangan mengikuti tutorial yang menonaktifkan sameSite atau httpOnly "biar lebih mudah". Dua atribut ini adalah dinding utama melawan XSS (httpOnly) dan CSRF (sameSite) — biarkan aktif di semua environment.

Checklist Hardening

Rekap kontrol yang sudah dan belum kalian miliki:

  • Helmet (secure headers + CSP)
  • CORS allowlist dari config
  • Validasi + sanitasi semua input (episode 13)
  • Parameterized query (episode 10)
  • Escaping template (episode 11)
  • bcrypt untuk password (episode 12)
  • Cookie HttpOnly + SameSite
  • Error handler tanpa detail internal (episode 7)
  • HTTPS di produksi (selalu, lewat reverse proxy/episode 24)
  • Rate limiting (episode 20)
  • Dependency audit rutin (episode 19)

Common Pitfalls

Trust Proxy Tidak Diatur

Tanpa trust proxy, req.ip dan secure cookie berperilaku salah saat aplikasi di belakang reverse proxy/load balancer — IP yang tercatat adalah IP proxy, bukan client. Atur di episode 25.

Header Keamanan Ganda

Memakai Helmet dan server web yang juga memasang header (misal nginx) bisa menghasilkan nilai ganda. Tentukan satu pemilik header.

Validasi Hanya Satu Lapis

Hanya mengandalkan validasi frontend atau hanya sanitasi backend meninggalkan celah. Pertahanan bertumpuk (template escaping + CSP + httpOnly) adalah norma.

Tip

Uji hardening dengan alat otomatis: npx helmet-check (memeriksa header) dan OWASP ZAP (scan otomatis). Jadikan ini bagian dari workflow sebelum release — bukan reaksi setelah insiden.

Penutup

Episode 18 menutup kategori serangan web utama: Helmet untuk header keamanan, CORS allowlist, SQLi/NoSQLi lewat parameterized query dan validasi, XSS lewat escaping + CSP + HttpOnly, serta CSRF lewat SameSite.

Inti yang harus dibawa pulang:

  • app.use(helmet()) memasang secure headers + CSP dalam satu baris.
  • CORS produksi memakai allowlist dari config; credentials tidak kompatibel dengan origin *.
  • SQLi dicegah parameterized query; NoSQLi dicegah validasi schema.
  • XSS: escape output + CSP + cookie HttpOnly (pertahanan bertumpuk).
  • CSRF: sameSite: "lax" menutup sebagian besar kasus; token untuk kasus ekstrem.
  • Daftar checklist hardening di atas adalah target akhir aplikasi produksi kalian.

Di episode 19 selanjutnya kita akan mengelola CVE & dependency management — contoh CVE nyata 2026, npm audit, dependabot, dan alur update dependency yang aman. Sampai jumpa di episode 19!

Belajar ExpressJS - Security Best Practices | Belajar ExpressJS