Security headers adalah instruksi yang ditempel server di setiap response, yang memberi tahu browser seketat apa halaman itu harus diperlakukan. Bayangin paket dengan stiker penanganan: "kirim lewat jalur aman saja", "cuma boleh berisi barang dari daftar ini", "isi sesuai label, jangan ditebak", "jangan dimasukkan ke kotak lain", "jangan tulis alamat lengkap pengirim", "kamera di dalamnya jangan dinyalakan". Kurirnya browser: dia membaca stikernya dan patuh. Gak ada stiker, kurirnya pakai kebiasaan sendiri, yang sering terlalu longgar.
🧪 Tebakan cepat:
belajarweb.examplemengirimStrict-Transport-Security: max-age=31536000. Apakahapi.belajarweb.exampleikut dipaksa lewat HTTPS? Tebak dulu, jawabannya ada di bagian "HSTS: selalu lewat HTTPS".
Enam header yang dinilai
Di Lab, response dari belajarweb.example mula-mula gak membawa satu pun security header, dan grader-nya memberi nilai F. Grader mengecek enam hal:
| Header | Nilai yang lulus | Mencegah |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
koneksi yang turun ke HTTP biasa |
Content-Security-Policy |
default-src 'self' (tanpa 'unsafe-inline') |
script asing yang ikut jalan (XSS) |
X-Frame-Options atau CSP frame-ancestors |
DENY atau frame-ancestors 'none' |
halaman dibingkai situs lain (clickjacking) |
X-Content-Type-Options |
nosniff |
browser menebak jenis file |
Referrer-Policy |
strict-origin-when-cross-origin atau no-referrer |
URL lengkap bocor ke situs lain |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
fitur browser dipakai tanpa perlu |
Enam lulus dapat A, lima B, empat C, tiga D, dua atau kurang F.
HSTS: selalu lewat HTTPS
Tanpa HSTS, orang yang mengetik belajarweb.example di address bar mampir dulu ke http://, lalu dialihkan ke https://. Permintaan pertama itu belum terenkripsi, dan bisa dibelokkan orang di jaringan yang sama. HSTS (HTTP Strict Transport Security) menyuruh browser mengingat: "situs ini cuma lewat HTTPS".
Strict-Transport-Security: max-age=31536000; includeSubDomains
max-age=31536000adalah lama browser mengingatnya, dalam detik: 31.536.000 detik = 365 hari. Nilai pendek sepertimax-age=300(5 menit) cepat lupa.includeSubDomainsmemberlakukan aturan yang sama ke semua subdomain. Tanpa itu,api.belajarweb.examplegak ikut dipaksa. Itu jawaban tebakannya: tidak, kecuali adaincludeSubDomains.- Tambahan
preloaddipakai untuk mendaftar ke daftar HSTS bawaan browser. Syaratnya:max-ageminimal setahun dan adaincludeSubDomains.
Browser cuma menghormati HSTS yang datang lewat HTTPS. Header yang sama di response http:// diabaikan.
CSP, nosniff dan anti-framing
- Content-Security-Policy membatasi dari mana script, gaya, gambar dan lainnya boleh dimuat.
default-src 'self'artinya cuma dari origin sendiri, dan inline script ikut diblokir. Kalauscript-srcberisi'unsafe-inline', perlindungan utamanya terhadap XSS hilang, jadi grader menandainya kuning. Detailnya ada di pelajaran Content Security Policy. - X-Content-Type-Options: nosniff menyuruh browser percaya pada
Content-Typedari server dan gak menebak. Tanpa itu, file.txthasil upload yang isinya mirip HTML bisa diperlakukan sebagai halaman. - X-Frame-Options: DENY (cara lama) atau
frame-ancestors 'none'di CSP (cara modern) melarang halaman dibingkai iframe situs lain. Ini pertahanan clickjacking dari pelajaran sebelumnya.
Referrer-Policy dan Permissions-Policy
Referrer-Policy mengatur seberapa banyak alamat halaman yang dikirim di header Referer waktu user mengklik link ke situs lain:
| Nilai | Yang dikirim ke situs lain |
|---|---|
unsafe-url |
URL lengkap, termasuk path dan query, misalnya https://belajarweb.example/reset?token=abc |
strict-origin-when-cross-origin |
cuma origin, https://belajarweb.example/ |
no-referrer |
gak ada sama sekali |
strict-origin-when-cross-origin adalah keseimbangan yang umum: situs sendiri masih dapat URL lengkap, situs lain cuma origin. Ini juga default browser modern, tapi tetap tulis eksplisit supaya gak bergantung pada default.
Permissions-Policy (dulu Feature-Policy) mengontrol fitur browser yang boleh dipakai halaman dan iframe di dalamnya. camera=() dengan kurung kosong artinya gak ada yang boleh, termasuk situs sendiri. geolocation=(self) artinya cuma origin sendiri.
Memasang di server
Di Express, satu middleware memasang semuanya di setiap response:
function securityHeaders(req, res, next) {
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
res.setHeader("Content-Security-Policy", "default-src 'self'; frame-ancestors 'none'");
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
next(); // lanjut ke handler berikutnya
}
app.use(securityHeaders);
res.setHeader(nama, nilai)menempelkan satu header ke response.next()wajib dipanggil. Tanpa itu, request berhenti di middleware ini dan gak pernah dijawab.app.use(securityHeaders)di paling atas bikin semua route dapat header-nya. Di proyek nyata, packagehelmetmemasang set header seperti ini sekaligus.
Di Nginx, tulis add_header dengan always, supaya header ikut terkirim di response error (404, 500) juga:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Header lama X-XSS-Protection jangan diandalkan: filter XSS bawaan browser sudah dihapus. Kalau dikirim, nilainya 0, dan perlindungan XSS-nya serahkan ke CSP. Cek hasilnya di DevTools (tab Network, klik request, bagian Response Headers), atau dengan pemindai seperti securityheaders.com dan Mozilla Observatory.
⚠️ Jebakan yang sering ketemu
- Lupa
next()di middleware. Header-nya terpasang, tapi request menggantung dan gak pernah dijawab. - Memasang header lewat
<meta>. Cuma CSP (tanpaframe-ancestors) dan referrer yang bisa lewat meta. HSTS,X-Frame-Options,nosniffdanPermissions-Policyharus header HTTP sungguhan. add_headerdi bloklocationNginx. Begitu satulocationpunyaadd_headersendiri, semuaadd_headerdari levelservergak diwariskan ke situ. Ulangi header-nya, atau taruh di satu file yang di-include.- HSTS tanpa
includeSubDomains, ataumax-agependek. Subdomain tetap bisa diakses lewat HTTP, dan browser cepat lupa.
TL;DR: Security headers adalah instruksi server ke browser di setiap response. Pasang enam: Strict-Transport-Security: max-age=31536000; includeSubDomains (HTTPS saja selama setahun, termasuk subdomain), Content-Security-Policy tanpa 'unsafe-inline', X-Frame-Options: DENY atau frame-ancestors 'none', X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin (mengontrol info referrer yang dikirim), dan Permissions-Policy: camera=(), microphone=() (mengontrol fitur browser). Di Express pakai res.setHeader di satu middleware lalu panggil next(); di Nginx pakai add_header ... always. X-XSS-Protection sudah usang: kalau dikirim, nilainya 0. Header ini harus header HTTP, bukan <meta>.