Siti sedang memeriksa halaman toko dengan DevTools: menu kanan → View Page Source, lalu tab Sources. Dalam satu menit ia membaca seluruh kode JavaScript halaman — kode yang ditulis tim, string konfigurasi, sampai komentar yang lupa dihapus. Tidak ada yang istimewa dari caranya; setiap pengunjung memiliki akses yang sama. Kode klien memang harus dikirim ke browser agar bisa dijalankan, dan begitu terkirim, kode itu sudah berada di tangan pengguna.
Kesimpulannya penting dan perlu ditegaskan sejak awal: tidak ada rahasia di kode klien. Chapter ini membuktikan pernyataan itu dengan uji sederhana, membongkar satu kesalahpahahan yang umum — environment variables yang disuntik saat build — lalu menunjukkan pola yang benar agar kunci API tetap berguna tanpa pernah menyentuh browser.
Kode Klien Adalah Kode Publik #
Mekanismenya sederhana: browser harus menerima kode sebelum bisa menjalankannya, dan apa pun yang diterima bisa dibaca. Minifikasi mengganti nama variabel dan merapikan spasi, tetapi tidak mengubah nilai — string tetap string. Source map bahkan bisa mengembalikan bentuk asli berkasnya bila ikut terkirim. Artinya, pemeriksaannya bukan “apakah kunci saya cukup tersembunyi?”, melainkan “apa saja yang saya titipkan di sisi klien?”.
Tab Sources bukan satu-satunya jendela. Tab Network di DevTools memperlihatkan setiap permintaan dan respons yang melintas — termasuk data yang server kembalikan untuk halaman. Karena itu prinsipnya menjangkau dua arah: rahasia tidak dikirim dari klien, dan data yang lebih sensitif dari kebutuhan tampilan tidak dikirim ke klien. Bila layar hanya menampilkan total, server tidak perlu mengirim rincian yang tidak dipakai UI.
Membuktikan: Token Bertahan Melewati Minifikasi #
Simulasi berikut dijalankan di terminal. Berkas memuat satu “kode sumber” berisi token fiktif (nilai GANTI_DENGAN_TOKEN_ANDA adalah penampung, bukan kunci sungguhan), lalu meniru dua pekerjaan minifier: spasi dirapikan dan nama variabel dipendekkan:
// cek-bundle.js — membuktikan bahwa string pada kode klien selalu ikut terkirim
const KODE_SUMBER = `
const PENGATURAN = {
urlApi: "https://api.toko.example/v2",
tokenRahasia: "GANTI_DENGAN_TOKEN_ANDA",
};
`;
// simulasi minifikasi: spasi dirapikan, nama variabel dipendekkan
const KODE_MINIFIED = KODE_SUMBER
.replace(/\s+/g, " ")
.replace("PENGATURAN", "p")
.replace("tokenRahasia", "t");
console.log("token masih ada:", KODE_MINIFIED.includes("GANTI_DENGAN_TOKEN_ANDA"));
console.log(KODE_MINIFIED.trim());Output yang Dihasilkan:
token masih ada: true
const p = { urlApi: "https://api.toko.example/v2", t: "GANTI_DENGAN_TOKEN_ANDA", };Mari kita bedah:
- Baris 2–7 (kode sumber): template literal berperan sebagai “berkas” yang nanti dikirim ke browser. Di dalamnya ada dua string: URL API yang memang boleh publik, dan token yang seharusnya rahasia.
- Baris 10–13 (simulasi minifikasi):
replace(/\s+/g, " ")merapikan seluruh spasi dan baris baru, duareplaceberikutnya meniru penggantian nama —PENGATURANmenjadip,tokenRahasiamenjadit. Persis pola yang dikerjakan minifier sungguhan pada nama variabel dan properti. - Baris 15 (pemeriksaan):
includesmencari teks token di hasil “minifikasi” — hasilnyatrue. Minifier mengganti nama, bukan nilai; string literal tidak ikut berganti. - Baris 16 (bentuk akhir): keluaran memperlihatkan “bundle” siap kirim: nama sudah pendek, token utuh di tempatnya. Siapa pun yang membuka tab Sources melihat baris yang sama.
Cara yang sama bekerja pada bundle produksi apa pun: buka tab Sources, salin isi berkasnya, dan cari kata kunci seperti key, secret, atau token. Temuan apa pun berarti nilai itu sudah publik — perlakuan penutupnya ada di catatan penting di akhir chapter.
Environment Variables Build-Time Tetap Terbaca #
Kesalahpahaman yang paling umum: “kuncinya tidak ditulis di kode, saya taruh di berkas .env”. Alat build modern memang membaca .env — dan menyuntik nilainya ke dalam bundle saat proses build. Konvensi penamaannya bahkan mengiyakan hal ini secara terbuka: variabel berawalan VITE_, NEXT_PUBLIC_, atau REACT_APP_ memang dirancang untuk publik. Sesudah build, hasil bundle berisi nilai itu apa adanya:
const KUNCI = "GANTI_DENGAN_KUNCI_API_ANDA"Env var build-time mengurus kenyamanan — nilai tidak diulang-ulang di kode sumber dan bisa berbeda per lingkungan — bukan kerahasiaan. Bila nilai itu boleh dilihat publik, pemakaian env var tidak masalah; bila tidak, nilainya tidak boleh berakhir di build klien, dalam bentuk apa pun.
Tabel berikut merangkum pembagian tugas untuk jenis nilai yang paling sering muncul:
| Nilai | Boleh di kode klien? | Tempat yang tepat |
|---|---|---|
| URL API publik | ya | kode klien, biasa saja |
| Kunci yang dirancang publik (publishable) | ya | kode klien, sesuai dokumentasi penyedianya |
| Secret key, token akses | tidak | environment server, lewat proxy |
| Kredensial basis data | tidak | environment server |
| Konfigurasi non-sensitif (tema, flag fitur) | ya | kode klien atau env build-time |
Solusi: Kunci Tinggal di Server #
Pola yang benar memindahkan panggilan berkunci ke server kita sendiri:
- Browser memanggil server sendiri tanpa kunci:
fetch("/api/cuaca?kota=Bandung"). - Server menyimpan kunci API di environment-nya (di mesin server, bukan di build klien), melengkapi permintaan, lalu meneruskannya ke API pihak ketiga.
- Server mengembalikan hanya data yang dibutuhkan halaman — sekaligus jadi tempat yang tepat untuk menyaring respons dan menegakkan batas pemakaian.
fetch("/api/cuaca?kota=Bandung")
.then((respons) => respons.json())
.then((data) => console.log(data.suhu));Dengan pola ini, kunci tidak pernah menyentuh browser, dan browser pun tidak perlu tahu bahwa ada kunci. Pola proxy juga membawa keuntungan samping yang layak diambil: rotasi kunci cukup dilakukan di server tanpa deploy ulang kode klien; kuota API ditegakkan di satu tempat; dan respons dari pihak ketiga bisa disaring sebelum sampai ke halaman. Kuncinya sendiri disimpan sebagai env var di lingkungan server — bukan di berkas yang ter-commit.
Ada dua kelompok kredensial yang perlu dibedakan:
- Kunci yang memang dirancang publik — misalnya kunci identitas integrasi peta atau pembayaran untuk sisi klien — boleh berada di kode klien; pembuatnya memang merancangnya untuk itu.
- Secret key, token akses, dan kredensial basis data tidak pernah berada di kode klien, di
.envbuild klien, maupun di storage browser — tempat terakhir ini dibahas di chapter Secure Browser Storage.
Sejauh ini pertahanan kita berupa kebiasaan menulis kode. Lapisan berikutnya ditegakkan oleh browser: kebijakan yang menyatakan dari mana skrip boleh berasal.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
cek-bundle.js
Unduh berkas// cek-bundle.js — membuktikan bahwa string pada kode klien selalu ikut terkirim
const KODE_SUMBER = `
const PENGATURAN = {
urlApi: "https://api.toko.example/v2",
tokenRahasia: "GANTI_DENGAN_TOKEN_ANDA",
};
`;
// simulasi minifikasi: spasi dirapikan, nama variabel dipendekkan
const KODE_MINIFIED = KODE_SUMBER
.replace(/\s+/g, " ")
.replace("PENGATURAN", "p")
.replace("tokenRahasia", "t");
console.log("token masih ada:", KODE_MINIFIED.includes("GANTI_DENGAN_TOKEN_ANDA"));
console.log(KODE_MINIFIED.trim());