API Security (CORS, CSRF, Input Sanitization) — API

API security itu bukan "satu fitur" — lebih mirip layered defense. Setiap lapisan menangani ancaman berbeda. CORS (Cross-Origin Resource Sharing) Browser…

API security itu bukan "satu fitur" — lebih mirip layered defense. Setiap lapisan menangani ancaman berbeda.

CORS (Cross-Origin Resource Sharing)

Browser otomatis memblokir request dari domain A ke domain B — kecuali server B explicit bilang "OK". Ini yang disebut CORS.

Tanpa CORS properly set, API kamu tidak bisa dipanggil dari frontend di domain berbeda.

// Express.js — setup CORS simple
import cors from "cors";

app.use(cors({
  origin: ["https://app.kamu.com", "https://admin.kamu.com"],
  credentials: true,  // allow cookies
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
}));

⚠️ Jangan pakai Access-Control-Allow-Origin: * dengan credentials. Itu berarti siapapun bisa pakai cookie user dari request mereka — data breach siap terjadi.

Preflight request: browser otomatis kirim OPTIONS dulu untuk request "non-simple" (PUT, DELETE, custom headers). Server harus respond dengan Access-Control-Allow-Methods, Access-Control-Allow-Headers.

CSRF (Cross-Site Request Forgery) — Masih Relevan?

CSRF: attacker menjebak user yang sudah login untuk memicu aksi di situs target. Contoh: user login di bank, attacker kirim email dengan image yang src-nya https://bank.com/transfer?to=attacker&amount=1000 — browser ikut kirim cookie, bank kira legit.

CSRF hanya relevan kalau autentikasi pakai cookie. Kalau pakai Bearer token di header (JWT), CSRF otomatis mitigated — JavaScript attacker tidak bisa baca atau kirim token dari origin lain.

3 lapis perlindungan kalau pakai cookie:

  1. SameSite cookie (paling simple, paling efektif):

    res.cookie("session", token, {
      httpOnly: true,
      secure: true,
      sameSite: "lax",  // "strict" untuk max security, "lax" untuk tetap berfungsi dengan cross-site navigation
    });
    
  2. CSRF token (double submit cookie atau synchronizer pattern):

    • Server kirim token random di cookie + form
    • Setiap POST/PUT/DELETE cek cocok
    • Laravel, Django, Rails otomatis handle ini
  3. Origin/Referer validation — cek Origin atau Referer header cocok domain kamu:

    if (req.method !== "GET" && req.headers.origin !== "https://app.kamu.com") {
      return res.status(403).send("Forbidden");
    }
    

Input Sanitization

Jangan pernah percaya input dari client. Semua yang masuk harus di-validasi + di-sanitize.

SQL Injection — parameterized queries wajib:

// ❌ BAHAYA: string interpolation
db.query(`SELECT * FROM users WHERE email = "${req.body.email}"`);
// attacker kirim: [email protected]"; DROP TABLE users; --

// ✅ Parameterized
db.query("SELECT * FROM users WHERE email = ?", [req.body.email]);
// driver escape nilai secara aman

XSS — sanitize sebelum output:

import DOMPurify from "isomorphic-dompurify";

// User posting comment dengan HTML
const sanitizedContent = DOMPurify.sanitize(req.body.content, {
  ALLOWED_TAGS: ["b", "i", "em", "strong", "a"],
  ALLOWED_ATTR: ["href"],
});

Command Injection — avoid shell execution dengan user input:

import { execFile } from "child_process";

// ❌ shell=true dengan user input
exec(`convert ${req.body.filename} output.png`);

// ✅ execFile dengan array args (no shell parsing)
execFile("convert", [req.body.filename, "output.png"]);

Path Traversal — validate path:

import path from "path";

const filename = path.basename(req.body.filename);  // buang ../
const safePath = path.join("/safe/dir", filename);
if (!safePath.startsWith("/safe/dir")) return res.status(400).end();

Rate Limiting per Endpoint Sensitivity

Login dan signup lebih sensitif daripada list produk. Pasang rate limit yang berbeda:

import rateLimit from "express-rate-limit";

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 menit
  max: 5,                     // 5 attempt per IP
  message: "Terlalu banyak percobaan login, coba lagi 15 menit lagi",
});

const apiLimiter = rateLimit({
  windowMs: 1 * 60 * 1000,    // 1 menit
  max: 60,
});

app.post("/auth/login", loginLimiter, handleLogin);
app.use("/api", apiLimiter);

Kombinasi per-IP + per-email penting untuk login — attacker bisa pakai 1000 IP, tapi target 1 email saja. Limit "5 attempt per email per 15 min" mencegah credential stuffing.

TLS / HTTPS — Basics

Pakai HTTPS untuk semua API production. Tanpa TLS, cookie/token bisa di-sniff di WiFi publik.

Security Headers Wajib

app.use((req, res, next) => {
  res.set({
    "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",                // anti-clickjacking
    "Content-Security-Policy": "default-src 'self'",
    "Referrer-Policy": "no-referrer",
    "Permissions-Policy": "geolocation=(), camera=()",
  });
  next();
});

// Atau pakai Helmet.js — semua header sekaligus
import helmet from "helmet";
app.use(helmet());

Checklist Production

Yang akan kamu pelajari