API Gateway & Security Headers — Security

API Gateway sebagai Lapisan Keamanan API Gateway adalah reverse proxy yang berdiri di depan API backend dan menyediakan fitur keamanan terpusat: autentikasi…

Security headers adalah header di response yang memberi tahu browser cara memperlakukan jawaban itu: jangan menebak jenis isinya, jangan tampilkan di dalam frame situs lain, selalu pakai HTTPS. API gateway adalah server yang berdiri di depan semua service API-mu, misalnya nginx, jadi semua response lewat situ dan header-nya cukup dipasang sekali. Bayangin label di paket: "Isi: dokumen. Jangan dipajang di etalase toko lain." Kurirnya (browser) menurut ke label itu, dan bagian pengiriman di depan gedung (gateway) menempelkan label yang sama ke setiap paket yang keluar, dari departemen mana pun.

🧪 Tebakan cepat: gateway nginx-mu memasang add_header X-Content-Type-Options "nosniff"; tanpa kata always. Response 200 OK membawa header itu. Response 404 Not Found membawanya juga atau gak? Jawabannya ada di bagian "Memasang di gateway nginx".

Header yang dipasang di setiap response API

Header Nilai Gunanya
X-Content-Type-Options nosniff browser wajib percaya Content-Type, gak boleh menebak jenis isinya
X-Frame-Options DENY atau SAMEORIGIN response gak boleh tampil di dalam <iframe> situs lain
Referrer-Policy strict-origin-when-cross-origin ke situs lain, browser cuma mengirim origin, bukan URL lengkap
Strict-Transport-Security max-age=31536000; includeSubDomains browser selalu pakai HTTPS selama 1 tahun
Cache-Control no-store response berisi data pribadi gak disimpan di cache

Ada juga header yang harus dibuang: X-Powered-By: Express dan versi di Server: nginx/1.27.0. Header itu memberi tahu penyerang software dan versi apa yang kamu pakai, jadi dia tinggal mencari celah untuk versi itu.

nosniff: percaya label, jangan menebak

MIME sniffing adalah kebiasaan browser menebak jenis isi dari isinya sendiri, bukan dari header Content-Type. Tebakan ini bisa dimanfaatkan. Misalnya endpoint GET /api/v1/notes/7 mengembalikan catatan yang ditulis user dengan Content-Type: text/plain. Halaman di situs lain menulis:

<script src="https://api.myapp.com/api/v1/notes/7"></script>

Kalau isi catatan itu kebetulan JavaScript yang valid, browser tanpa nosniff mau menjalankannya sebagai script. Dengan X-Content-Type-Options: nosniff, browser memegang label text/plain dan menolak. Chrome menulis di console:

Refused to execute script from 'https://api.myapp.com/api/v1/notes/7' because its MIME type ('text/plain') is not executable, and strict MIME type checking is enabled.

Aturan yang sama berlaku untuk stylesheet: dengan nosniff, file yang Content-Type-nya bukan text/css gak dipakai sebagai CSS. Nilainya cuma satu: nosniff.

X-Frame-Options: DENY atau SAMEORIGIN

X-Frame-Options mengatur siapa yang boleh menampilkan response-mu di dalam <iframe>:

Di pelajaran clickjacking kamu sudah kenal versi modernnya, CSP frame-ancestors 'none' (sama dengan DENY) dan frame-ancestors 'self' (sama dengan SAMEORIGIN). Memasang keduanya aman: browser modern memakai frame-ancestors, browser lama memakai X-Frame-Options.

Memasang di gateway nginx

Di gateway, header dipasang dengan add_header, dan info server disembunyikan dengan dua baris:

server {
    listen 443 ssl;
    server_name api.myapp.com;

    server_tokens off;
    proxy_hide_header X-Powered-By;

    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;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location /api/ {
        proxy_pass http://backend:8000;
    }
}

Hasilnya, GET /api/v1/users/42 dan GET /api/v1/users/999 (404) sama-sama tiba dengan:

Server: nginx
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains

Di level aplikasi: middleware Express

Kalau gak ada gateway, pasang header yang sama di aplikasi. Ini middleware Express yang jalan sebelum semua route:

app.disable("x-powered-by"); // buang X-Powered-By: Express

app.use((req, res, next) => {
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
  res.setHeader("Cache-Control", "no-store");
  next();
});

Package helmet memasang sebagian besar header ini sekaligus dengan app.use(helmet()).

💡 Gateway juga mengurus rate limit dan ukuran request

Gateway juga tempat yang pas untuk membatasi jumlah request (kamu sudah belajar alasannya di rate-limiting-security) dan ukuran body. Perhatikan letaknya: limit_req_zone wajib di blok http. Ditaruh di blok server, nginx menolak start dengan pesan "limit_req_zone" directive is not allowed here.

http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    server {
        server_name api.myapp.com;

        location /api/ {
            limit_req zone=api burst=20 nodelay;
            client_max_body_size 10m;
            proxy_pass http://backend:8000;
        }
    }
}

⚠️ Jebakan yang sering ketemu

TL;DR: Security headers adalah instruksi di response untuk browser, dan API gateway seperti nginx memasangnya sekali untuk semua service. Pasang X-Content-Type-Options: nosniff (browser gak boleh menebak jenis isi, jadi text/plain gak bisa jalan sebagai script), X-Frame-Options: DENY (atau SAMEORIGIN kalau origin sendiri boleh mem-frame), Referrer-Policy: strict-origin-when-cross-origin, dan Strict-Transport-Security. Buang X-Powered-By dan versi Server dengan proxy_hide_header dan server_tokens off. Di nginx, tulis always supaya header ikut di 404 dan 500, dan ingat add_header di location menghapus warisan dari server. Di Express, pasang header dengan res.setHeader di middleware dan matikan x-powered-by.

Yang akan kamu pelajari