Sepanjang Section ini, semua percakapan kita berjalan dalam HTTP polos: teks yang bisa dibaca siapa pun yang berada di jalur antara klien dan server. Untuk aplikasi sungguhan, kondisi tersebut tidak bisa diterima — kata sandi, token, dan data pribadi harus dilindungi sejak meninggalkan proses. Perlindungan itu datang dari TLS (Transport Layer Security), protokol yang mengenkripsi koneksi dan memverifikasi identitas lawan bicara, sementara HTTP tetap menjadi bahasa percakapan di atasnya. Pada chapter penutup ini kita pelajari cara kerja TLS, peran sertifikat, lalu membangun server HTTPS dengan sertifikat buatan sendiri untuk keperluan pengembangan.
Apa yang Ditambahkan TLS #
TLS menyelipkan satu lapisan di antara TCP dan HTTP. Saat klien terhubung, kedua pihak menjalankan TLS handshake: saling sepakat versi dan sandi, server menunjukkan sertifikat sebagai bukti identitas, lalu keduanya menurunkan session key bersama yang dipakai mengenkripsi seluruh data berikutnya. Setelah handshake selesai, HTTP berjalan seperti biasa — method, header, body — tetapi semua byte yang melintas sudah berbentuk terenkripsi. Tiga jaminan yang diberikan:
| Jaminan | Artinya |
|---|---|
| Kerahasiaan | Penyadap jalur jaringan hanya melihat data terenkripsi |
| Identitas | Sertifikat membuktikan bahwa kita terhubung ke server yang benar |
| Integritas | Data yang diubah di tengah jalan terdeteksi otomatis |
Alamat dengan skema https dan port standar 443 menandai koneksi semacam ini. Gambaran umum protokol TLS untuk web terdokumentasi di MDN tentang Transport Layer Security
.
Sertifikat dan Rantai Kepercayaan #
Sertifikat adalah dokumen digital yang memuat identitas (nama domain), kunci publik server, masa berlaku, dan tanda tangan pihak yang mengesahkan. Klien tidak percaya pada sertifikat apa pun — klien percaya pada rantai kepercayaan: sertifikat daun (leaf) milik server ditandatangani intermediate CA, intermediate ditandatangani root CA, dan root CA yang tersimpan di penyimpan kepercayaan sistem operasi atau browser-lah titik awal kepercayaannya. Ketika kita membuka https://nodejs.org, browser memeriksa rantai ini sampai akar sebelum koneksi diizinkan.
Untuk pengembangan lokal, kita bisa menerbitkan sertifikat yang menandatangani dirinya sendiri (self-signed): satu perintah openssl membuat pasangan kunci sekaligus sertifikat bertanda tangan sendiri dengan identitas localhost:
openssl req -x509 -newkey rsa:2048 -nodes -keyout kunci.pem -out sert.pem -days 1 -subj '/CN=localhost'Tiap opsi punya peran yang perlu kita pahami:
| Opsi | Arti |
|---|---|
req -x509 | Menerbitkan sertifikat X.509 yang menandatangani dirinya sendiri |
-newkey rsa:2048 | Membuat pasangan kunci RSA baru sepanjang 2048 bit |
-nodes | Kunci privat disimpan tanpa perlindungan kata sandi |
-keyout kunci.pem -out sert.pem | Berkas keluaran untuk kunci privat dan sertifikat |
-days 1 | Sertifikat berlaku satu hari |
-subj '/CN=localhost' | Identitas pemilik sertifikat: localhost |
Server TLS Mentah dengan node:tls #
Module node:tls
menyediakan lapisan TLS mentah tanpa HTTP — berguna untuk protokol kustom yang juga butuh enkripsi. Echo server berikut menerima koneksi terenkripsi dan memantulkan kembali setiap data yang diterima. Karena sertifikat demo sengaja dibuat hanya untuk sesi ini, blok perintah berikut membuatnya lebih dulu, menjalankan demo, lalu membersihkan berkasnya:
openssl req -x509 -newkey rsa:2048 -nodes -keyout kunci.pem -out sert.pem -days 1 -subj '/CN=localhost'
node echo-tls.mjsOutput yang Dihasilkan:
diterima: halo lewat TLS
protokol: TLSv1.3Kode servernya sendiri ringkas:
import tls from 'node:tls';
import fs from 'node:fs';
const server = tls.createServer(
{
key: fs.readFileSync('kunci.pem'),
cert: fs.readFileSync('sert.pem'),
},
(soket) => {
soket.on('data', (data) => soket.write(data));
soket.on('end', () => soket.end());
},
);
server.listen(0, '127.0.0.1', () => {
const port = server.address().port;
const klien = tls.connect(
{ host: '127.0.0.1', port, rejectUnauthorized: false },
() => {
klien.write('halo lewat TLS');
klien.end();
},
);
klien.on('data', (data) => console.log('diterima:', data.toString('utf-8')));
klien.on('end', () => {
console.log('protokol:', klien.getProtocol());
server.close();
});
});Mari kita telaah bagian-bagian utamanya:
- Baris 4-8 (
tls.createServer({...}, penangan)): berbeda denganhttp.createServer, server TLS wajib menerima opsikeydancert— kunci privat dan sertifikat yang dipakai saat handshake. Keduanya dibaca sinkron karena harus tersedia sebelum server mendengarkan koneksi. - Baris 9-12 (penangan soket): yang diterima bukan
req/res, melainkan soket terenkripsi. Kode ini memantulkan data (soket.write) dan menutup koneksi dengan rapi ketika sisi lain selesai. - Baris 18-24 (
tls.connect): sisi klien membangun koneksi TLS ke server. OpsirejectUnauthorized: falsemematikan verifikasi rantai kepercayaan — satu-satunya cara bertemu dengan sertifikat self-signed kita. - Baris 26-30: data terenkripsi tiba, diterjemahkan dari
Bufferke teks, lalu protokol hasil kesepakatan handshake dicetak —TLSv1.3, versi TLS yang menjadi bawaan saat ini.
HTTPS Server dan Klien #
Kembali ke dunia web: https.createServer
bekerja persis seperti http.createServer, hanya dengan opsi kunci dan sertifikat tambahan. Demo berikut menjalankan server HTTPS lalu mencobanya dari dua klien yang berbeda sikapnya terhadap sertifikat self-signed:
openssl req -x509 -newkey rsa:2048 -nodes -keyout kunci.pem -out sert.pem -days 1 -subj '/CN=localhost'
node server-https.mjsOutput yang Dihasilkan:
fetch : menolak sertifikat sendiri, DEPTH_ZERO_SELF_SIGNED_CERT
https.get: 200 TLSv1.3
CN sert : localhost
isi : {"aman":true,"jalur":"/tugas"}import https from 'node:https';
import { readFile } from 'node:fs/promises';
const kunci = await readFile('kunci.pem');
const sertifikat = await readFile('sert.pem');
const server = https.createServer(
{ key: kunci, cert: sertifikat },
(req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ aman: true, jalur: req.url }));
},
);
server.listen(0, '127.0.0.1', async () => {
const port = server.address().port;
try {
await fetch(`https://127.0.0.1:${port}/tugas`);
console.log('fetch : berhasil');
} catch (e) {
console.log('fetch : menolak sertifikat sendiri,', e.cause.code);
}
await new Promise((selesai) => {
https.get(
{ host: '127.0.0.1', port, path: '/tugas', rejectUnauthorized: false },
(res) => {
const soket = res.socket;
let isi = '';
res.on('data', (c) => (isi += c));
res.on('end', () => {
console.log('https.get:', res.statusCode, soket.getProtocol());
console.log('CN sert :', soket.getPeerCertificate().subject.CN);
console.log('isi :', isi);
selesai();
});
},
);
});
server.close();
});Mari kita telaah bagian-bagian utamanya:
- Baris 4-5 (
await readFile): kunci dan sertifikat dimuat sebagaiBufferdi level teratas module —awaitpada level teratas adalah fitur ESM yang sudah kita kenal. - Baris 7-13: penangan request identik dengan server HTTP biasa; perbedaan seluruhnya berada di lapisan transport, tepat seperti janji TLS: HTTP tidak perlu berubah.
- Baris 18-23 (
fetch): klien modern menolak sertifikat self-signed — errorDEPTH_ZERO_SELF_SIGNED_CERTterjadi karena rantai kepercayaan berhenti pada sertifikat itu sendiri. Perilaku ini sengaja sulit dimatikan padafetch; ia meneladankan ketegasan browser. - Baris 25-39 (
https.get): kliennode:httpsyang memilih mematikan verifikasi lewatrejectUnauthorized: falseberhasil terhubung, membaca status200, protokolTLSv1.3, dan identitasCNdari sertifikat lawan bicara lewatgetPeerCertificate()— data yang biasanya diperiksa otomatis oleh proses verifikasi.
Rangkaian yang Sudah Kita Pegang #
Kita mulai Section ini dari anatomi pesan HTTP, lalu membangun server, router, pembaca body, pengatur response, klien fetch, dan server berkas statis — kini ditutup dengan lapisan enkripsi dan identitas. Perjalanan itu adalah bekal lengkap untuk topik berikutnya yang lebih terstruktur: merancang dan membangun REST API — endpoint, CRUD, validasi, hingga dokumentasi — di atas fondasi yang sudah berdiri kokoh.