Checklist Wajib Sebelum Melepas REST API ke Production

I Nyoman Donostia 814 kata
Checklist Wajib Sebelum Melepas REST API ke Production

Kenapa Checklist Ini Penting?

Saya pernah melepas API ke production tanpa checklist. Hasilnya? Incident tengah malam: database kena brute force, response time melonjak 5x karena tidak ada rate limit, dan leak API key di commit history Git. Sejak saat itu, saya buat checklist ini dan jalankan setiap rilis.

1. Security: Autentikasi & Authorization

JWT Configuration yang Benar

// Node.js - Best practice JWT
const jwt = require('jsonwebtoken');

const token = jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,
    {
        expiresIn: '15m',           // Access token pendek
        algorithm: 'HS256',         // Explicit
        issuer: 'myapi.com',
        audience: user.role,
    }
);

// Refresh token terpisah, longer-lived
const refreshToken = jwt.sign(
    { userId: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: '7d', algorithm: 'HS256' }
);

✅ Checklist:

  • Token kadaluarsa < 1 jam (access token)
  • Refresh token mekanisme tersedia
  • Algoritma explicit (HS256 / RS256), tidak boleh none
  • Secret key panjang minimal 32 karakter
  • Validasi iss (issuer) dan aud (audience)

Password Hashing

const bcrypt = require('bcrypt');
// ⚠️ JANGAN pernah simpan plain text!
const hashedPassword = await bcrypt.hash(password, 12); // cost factor 12

2. Rate Limiting & Throttling

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,  // 15 menit
    max: 100,                   // 100 request per window per IP
    standardHeaders: true,
    legacyHeaders: false,
    message: {
        success: false,
        error: { code: 429, message: 'Too many requests' }
    }
});

// Limit lebih ketat untuk endpoint sensitif
const authLimiter = rateLimit({
    windowMs: 60 * 60 * 1000,   // 1 jam
    max: 5,                      // 5 percobaan login per jam per IP
});

app.use('/api/', apiLimiter);
app.use('/auth/login', authLimiter);

✅ Checklist:

  • Global rate limit pasang (~100 req/15min)
  • Endpoint login: max 5-10 percobaan per jam
  • Endpoint reset password: max 3 per jam
  • Beri header RateLimit-* untuk client awareness

3. HTTP Security Headers

const helmet = require('helmet');
app.use(helmet());
// Atau konfigurasi spesifik
app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "'trusted.cdn.com'"],
        }
    },
    crossOriginEmbedderPolicy: false,
}));

Header yang harus ada di response:

Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0               // Modern, rely on CSP
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Referrer-Policy: no-referrer-when-downgrade

4. CORS Policy yang Aman

const cors = require('cors');

app.use(cors({
    origin: (origin, callback) => {
        const whitelist = ['https://app.example.com', 'https://admin.example.com'];
        if (!origin || whitelist.includes(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
    allowedHeaders: ['Authorization', 'Content-Type'],
    maxAge: 86400                  // Cache preflight 24 jam
}));

⚠️ Jangan: origin: '*' jika credentials: true. Browser menolak dan security vulnerability!

5. Environment Variables & Secrets Management

File .env.example

# Database
DATABASE_URL=postgresql://user:pass@host:5432/dbname

# JWT Secrets (MIN 32 char)
JWT_SECRET=use-openssl-rand-hex-32-to-generate
REFRESH_TOKEN_SECRET=another-32-char-random-string

# API Rate Limit
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100

# Service keys
STRIPE_SECRET_KEY=sk_test_xxxxxxx
REDIS_URL=redis://localhost:6379

# Environment
NODE_ENV=production
PORT=3000

Generate Secret yang Kuat

# Generate dengan OpenSSL
openssl rand -hex 32

✅ Checklist:

  • .env masuk .gitignore
  • Tidak ada secret hardcode di source code
  • Rotasi secret tiap 3-6 bulan
  • Gunakan Vault (AWS Secrets Manager, Doppler) untuk production
  • Scan repo: gitleaks atau git-secrets

6. Input Validation

const { body, validationResult } = require('express-validator');

app.post('/users', 
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 12 }).withMessage('Password minimal 12 karakter'),
    body('name').trim().isLength({ min: 2, max: 100 }),
    body('age').isInt({ min: 18, max: 120 }),
    (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(422).json({ errors: errors.array() });
        }
        // ... handle valid input
    }
);

7. Logging & Monitoring

const morgan = require('morgan');
app.use(morgan('combined', { stream: winston.stream }));

app.use((req, res, next) => {
    const start = Date.now();
    res.on('finish', () => {
        logger.info({
            method: req.method,
            url: req.url,
            status: res.statusCode,
            duration: Date.now() - start,
            ip: req.ip,
            userAgent: req.get('User-Agent'),
        });
    });
    next();
});

✅ Checklist:

  • Logging terstruktur (JSON, bukan plain text)
  • JANGAN log informasi sensitif (password, token, PII)
  • Health check endpoint: GET /health return 200 OK
  • Metrics endpoint untuk Prometheus
  • Alerting otomatis (Sentry, Datadog, atau Prometheus AlertManager)

8. Database Indexing

-- Index untuk foreign key
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_orders_status ON orders(status, created_at);

✅ Checklist:

  • Explain plan untuk query paling sering
  • Composite index untuk WHERE ... ORDER BY
  • Diagonal index untuk query range (created_at BETWEEN ...)

Kesimpulan

Checklist ini bukan opsional — itu standar minimum. Setelah Anda memublikasikan API di production, attacker otomatis menemukan endpoint Anda. Tanpa helm, tanpa rate limit, tanpa CORS yang tepat — API Anda open season untuk abuse.

Print checklist ini, tempel di meja Anda, dan ceklis sebelum setiap rilis.

I Nyoman Donostia

I Nyoman Donostia

Fullstack Developer - Bali, Indonesia

Artikel Terkait