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
- Rotate segera — Ganti semua secrets yang bocor
- Audit access — Cek apakah ada akses tidak sah
- Bersihkan git history — Gunakan
git filter-branchatau BFG Repo Cleaner - Revoke tokens — Invalidate semua API keys dan tokens yang 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
- Build-time var — di-inline ke bundle saat build. Tidak bisa diubah tanpa rebuild. Semua var public di Vite/Next masuk sini.
- Runtime var — dibaca dari
process.envsaat kode server jalan. Bisa diubah via environment hosting (Vercel/Railway) tanpa rebuild. Lebih aman untuk rotasi.
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?
- URL public (domain, CDN, endpoint API)
- Key yang memang didesain publik — Supabase anon key, Firebase config, PostHog key, Sentry DSN, Stripe publishable key (
pk_live_*) - Feature flag default non-sensitive
Rule of thumb: kalau ada kata "secret", "private", atau "service role" di nama key, ia tidak boleh prefix VITE_/NEXT_PUBLIC_.
Best Practices
- Jangan pernah commit secrets ke git
- Gunakan .env.example sebagai template
- Gunakan secret manager di production
- Rotate secrets secara berkala
- Gunakan tools pre-commit untuk mendeteksi secrets (git-secrets, truffleHog)
- Berikan akses minimal — setiap service hanya punya secrets yang dibutuhkan
- Audit bundle frontend produksi — cari string seperti
sk_live_,sk-,AKIA(pattern AWS key)