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:

JaminanArtinya
KerahasiaanPenyadap jalur jaringan hanya melihat data terenkripsi
IdentitasSertifikat membuktikan bahwa kita terhubung ke server yang benar
IntegritasData 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:

bash
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:

OpsiArti
req -x509Menerbitkan sertifikat X.509 yang menandatangani dirinya sendiri
-newkey rsa:2048Membuat pasangan kunci RSA baru sepanjang 2048 bit
-nodesKunci privat disimpan tanpa perlindungan kata sandi
-keyout kunci.pem -out sert.pemBerkas keluaran untuk kunci privat dan sertifikat
-days 1Sertifikat 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:

bash
openssl req -x509 -newkey rsa:2048 -nodes -keyout kunci.pem -out sert.pem -days 1 -subj '/CN=localhost'
node echo-tls.mjs

Output yang Dihasilkan:

text
diterima: halo lewat TLS
protokol: TLSv1.3

Kode servernya sendiri ringkas:

js
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:

  1. Baris 4-8 (tls.createServer({...}, penangan)): berbeda dengan http.createServer, server TLS wajib menerima opsi key dan cert — kunci privat dan sertifikat yang dipakai saat handshake. Keduanya dibaca sinkron karena harus tersedia sebelum server mendengarkan koneksi.
  2. 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.
  3. Baris 18-24 (tls.connect): sisi klien membangun koneksi TLS ke server. Opsi rejectUnauthorized: false mematikan verifikasi rantai kepercayaan — satu-satunya cara bertemu dengan sertifikat self-signed kita.
  4. Baris 26-30: data terenkripsi tiba, diterjemahkan dari Buffer ke 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:

bash
openssl req -x509 -newkey rsa:2048 -nodes -keyout kunci.pem -out sert.pem -days 1 -subj '/CN=localhost'
node server-https.mjs

Output yang Dihasilkan:

text
fetch    : menolak sertifikat sendiri, DEPTH_ZERO_SELF_SIGNED_CERT
https.get: 200 TLSv1.3
CN sert   : localhost
isi       : {"aman":true,"jalur":"/tugas"}
js
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:

  1. Baris 4-5 (await readFile): kunci dan sertifikat dimuat sebagai Buffer di level teratas module — await pada level teratas adalah fitur ESM yang sudah kita kenal.
  2. Baris 7-13: penangan request identik dengan server HTTP biasa; perbedaan seluruhnya berada di lapisan transport, tepat seperti janji TLS: HTTP tidak perlu berubah.
  3. Baris 18-23 (fetch): klien modern menolak sertifikat self-signed — error DEPTH_ZERO_SELF_SIGNED_CERT terjadi karena rantai kepercayaan berhenti pada sertifikat itu sendiri. Perilaku ini sengaja sulit dimatikan pada fetch; ia meneladankan ketegasan browser.
  4. Baris 25-39 (https.get): klien node:https yang memilih mematikan verifikasi lewat rejectUnauthorized: false berhasil terhubung, membaca status 200, protokol TLSv1.3, dan identitas CN dari sertifikat lawan bicara lewat getPeerCertificate() — 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.