Environment Variables & Secrets — Security

Mengelola Secrets dengan Aman Secrets (API keys, database credentials, encryption keys) harus disimpan terpisah dari kode. Menyimpan secrets di kode sumber adal

Mengelola Secrets dengan Aman

Secrets (API keys, database credentials, encryption keys) harus disimpan terpisah dari kode. Menyimpan secrets di kode sumber adalah salah satu kesalahan keamanan paling umum dan berbahaya.

Kesalahan yang Sering Terjadi

// BURUK: hardcoded secrets di kode
const dbPassword = "super_secret_password_123";
const apiKey = "sk_live_abc123def456";

// BURUK: commit .env ke git
git add .env
git commit -m "add config"  // Secrets sekarang di git history SELAMANYA!

// BAIK: gunakan environment variables
const dbPassword = process.env.DB_PASSWORD;
const apiKey = process.env.API_KEY;

.env File dan .gitignore

# .gitignore — WAJIB ada
.env
.env.local
.env.production
*.pem
*.key

# .env — hanya di local, JANGAN commit
DB_HOST=localhost
DB_PASSWORD=my_secret
API_KEY=sk_live_abc123
JWT_SECRET=random_256_bit_string

# .env.example — template TANPA nilai sensitif (boleh commit)
DB_HOST=localhost
DB_PASSWORD=
API_KEY=
JWT_SECRET=

Secret Management di Production

1. Platform Secrets

# Vercel
vercel env add DB_PASSWORD production

# Railway / Render / Fly.io
# Gunakan dashboard atau CLI untuk set secrets

# Docker
docker run -e DB_PASSWORD=secret myapp

# Kubernetes
kubectl create secret generic app-secrets \
    --from-literal=DB_PASSWORD=secret

2. Secret Manager Services

// AWS Secrets Manager
const client = new SecretsManagerClient({ region: "ap-southeast-1" });
const secret = await client.send(new GetSecretValueCommand({
    SecretId: "myapp/production/db"
}));

// HashiCorp Vault
const vault = require("node-vault")();
const result = await vault.read("secret/data/myapp");
const dbPassword = result.data.data.DB_PASSWORD;

Jika Secret Bocor

Frontend Env Vars: Jebakan yang Sering Terjadi

Framework frontend modern membedakan env var yang public (dibundle ke browser) dan yang private (hanya ada di server build). Salah kaprah yang paling sering: developer mengira VITE_* atau NEXT_PUBLIC_* "lebih aman" karena tidak ada di repo git — padahal nilainya ter-hardcode di JavaScript yang di-serve ke browser.

Vite: VITE_*

# .env.local
VITE_SUPABASE_URL=https://xxx.supabase.co     # ini OK publik
VITE_SUPABASE_ANON_KEY=eyJhbG...               # OK (anon key memang publik)
VITE_STRIPE_SECRET_KEY=sk_live_xxx             # BAHAYA — ini rahasia!
DATABASE_URL=postgres://user:pw@host/db        # aman — tidak prefix VITE_

Saat npm run build, Vite mengganti semua import.meta.env.VITE_STRIPE_SECRET_KEY dengan literal string di bundle JavaScript. Buka DevTools → Sources → cari bundle → secret kamu terlihat jelas. Bukan obfuscated, bukan encrypted.

Next.js: NEXT_PUBLIC_*

# .env
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx         # OK publik (analytics)
NEXT_PUBLIC_API_URL=https://api.me.com  # OK publik (URL saja)
NEXT_PUBLIC_OPENAI_API_KEY=sk-xxx       # BAHAYA — bocor ke semua user!
OPENAI_API_KEY=sk-xxx                   # aman — server-only, pakai di API route

Build-time vs Runtime

Red Flags di Frontend

// BURUK: API key di komponen React
function Chat() {
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    headers: { "Authorization": `Bearer ${import.meta.env.VITE_OPENAI_KEY}` }
  });
}

// BAIK: panggil backend sendiri sebagai proxy
function Chat() {
  const res = await fetch("/api/chat", { method: "POST", body: ... });
}

Pola BFF (Backend-For-Frontend)

Kalau frontend harus manggil service pihak ketiga yang butuh secret, buat proxy endpoint di backend. Frontend manggil /api/send-email → backend validasi user, rate-limit, lalu panggil SendGrid dengan API key yang aman di server. Frontend tidak pernah melihat key.

Apa yang Aman Ditaruh di Public Env?

Rule of thumb: kalau ada kata "secret", "private", atau "service role" di nama key, ia tidak boleh prefix VITE_/NEXT_PUBLIC_.

Best Practices

Yang akan kamu pelajari