Pipeline TokoKita berjalan lewat banyak identitas sekaligus: service account ETL yang menarik data dari PostgreSQL, dashboard analyst yang membaca warehouse, dan webhook pembayaran yang mengirim event pesanan. Setiap permintaan dari identitas-identitas tersebut harus melewati dua gerbang berbeda: authentication menjawab “siapa yang meminta”, authorization menjawab “apa yang boleh dilakukan”. Kedua istilah ini sering tertukar, padahal kegagalan pada masing-masing membuka celah yang berbeda. Chapter ini membedah keduanya lewat simulasi token bertanda tangan HMAC yang meniru konsep JWT — lengkap dengan skenario token dimanipulasi dan token kedaluwarsa yang ditolak nyata oleh kode.

Authentication: Membuktikan Identitas #

Authentication memverifikasi identitas pelaku dengan satu atau lebih faktor: sesuatu yang diketahui (kata sandi), sesuatu yang dimiliki (kunci API, perangkat OTP), atau sesuatu yang melekat (biometrik). Untuk manusia, praktik yang layak adalah kata sandi yang disimpan sebagai hash ber-salt seperti pada Chapter Prinsip Keamanan Data, diperkuat multi-factor authentication (MFA) bila akun tersebut dapat mengubah data.

Untuk mesin, pola yang umum adalah kredensial tetap (API key) atau token berumur pendek. Token berumur pendek lebih aman: nilai tersebut berhenti berlaku sendiri setelah beberapa menit atau jam, sehingga pencuri token hanya punya jendela sempit. OAuth2 misalnya menerbitkan access token berumur pendek dan refresh token yang dapat dicabut. Service account pipeline pun sebaiknya memakai token, bukan kata sandi utama database yang samar dan jarang diganti.

Authorization: Memutuskan Hak Akses #

Authorization dievaluasi setelah identitas diketahui: kebijakan memutuskan apakah identitas tersebut boleh melakukan aksi ini pada sumber daya ini. Kebijakan paling aman ditulis deny by default — semua ditolak kecuali yang eksplisit diizinkan — sehingga fitur baru yang lupa menulis aturan akan gagal tertutup, bukan terbuka. HTTP memberi kode yang membedakan kedua gerbang tersebut: 401 Unauthorized berarti identitas belum terbukti, 403 Forbidden berarti identitas sah tetapi tidak berhak.

AspekAuthenticationAuthorization
PertanyaanSiapa yang meminta?Apa yang boleh dilakukan?
Waktu evaluasisekali di awal sesi atau permintaansetiap permintaan
Kode kegagalan401 Unauthorized403 Forbidden
Contoh mekanismekata sandi + MFA, token bertanda tanganscope, RBAC, ACL

Simulasi Token Bertanda Tangan HMAC #

JWT menata token sebagai tiga bagian base64url yang dipisah titik: header.payload.signature. Server menandatangani payload dengan HMAC memakai kunci rahasia; penerima menghitung ulang tanda tangan dan membandingkannya dengan hmac.compare_digest agar perbandingan tidak bisa dipancing bocor bit demi bit. Mari kita tiru mekanisme tersebut utuh:

python
"""Demo ch2: token bertanda tangan HMAC (konsep JWT) + keputusan authorization."""
import base64
import hashlib
import hmac
import json

# Kunci demo eksplisit untuk latihan; di production kunci dititipkan
# ke secrets manager, tidak pernah ditulis di dalam kode.
KUNCI = b"demo-signing-key-GANTI-DI-PRODUCTION"
WAKTU_SEKARANG = 1792857600  # simulasi: 1 Oktober 2026


def b64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()


def buat_token(klaim: dict) -> str:
    header = b64url(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
    payload = b64url(json.dumps(klaim).encode())
    pesan = f"{header}.{payload}".encode()
    tanda = b64url(hmac.new(KUNCI, pesan, hashlib.sha256).digest())
    return f"{header}.{payload}.{tanda}"


def verifikasi_token(token: str) -> dict | None:
    bagian = token.split(".")
    if len(bagian) != 3:
        return None
    header, payload, tanda = bagian
    pesan = f"{header}.{payload}".encode()
    tanda_diharapkan = b64url(hmac.new(KUNCI, pesan, hashlib.sha256).digest())
    if not hmac.compare_digest(tanda, tanda_diharapkan):
        return None  # tanda tangan tidak cocok -> token dipalsukan
    klaim = json.loads(base64.urlsafe_b64decode(payload + "=="))
    if klaim.get("exp", 0) < WAKTU_SEKARANG:
        return None  # token kedaluwarsa
    return klaim


def cek_akses(token: str, scope: str) -> str:
    klaim = verifikasi_token(token)
    if klaim is None:
        return "401 Unauthorized: token tidak valid"
    if scope not in klaim["scopes"]:
        return f"403 Forbidden: {klaim['sub']} tanpa scope {scope}"
    return f"200 OK: {klaim['sub']} diizinkan {scope}"


# 1) Terbitkan token untuk service account pipeline ETL TokoKita
klaim_etl = {"sub": "pipeline-etl", "exp": 1798761600,
             "scopes": ["pesanan:read", "pelanggan:read"]}
token = buat_token(klaim_etl)
print("token pipeline-etl :", token[:38] + "...")

# 2) Authentication + authorization: permintaan di dalam scope
print(cek_akses(token, "pesanan:read"))

# 3) Authentication berhasil, authorization menolak scope yang tidak ada
print(cek_akses(token, "pesanan:delete"))

# 4) Payload dimanipulasi pihak lain, tanda tangan tidak ikut diganti
header, payload, tanda = token.split(".")
payload_palsu = b64url(json.dumps(
    {"sub": "pengguna-palsu", "exp": 1798761600,
     "scopes": ["pesanan:delete"]}).encode())
token_palsu = f"{header}.{payload_palsu}.{tanda}"
print(cek_akses(token_palsu, "pesanan:delete"))

# 5) Token lama yang sudah kedaluwarsa
token_lama = buat_token({"sub": "pipeline-etl", "exp": 1767225600,
                         "scopes": ["pesanan:read"]})
print(cek_akses(token_lama, "pesanan:read"))

Output yang Dihasilkan:

text
token pipeline-etl : eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVC...
200 OK: pipeline-etl diizinkan pesanan:read
403 Forbidden: pipeline-etl tanpa scope pesanan:delete
401 Unauthorized: token tidak valid
401 Unauthorized: token tidak valid

Mari kita telaah baris-baris kode di atas:

  1. Baris 18-23 (buat_token): fungsi ini merakit header.payload, menghitung HMAC-SHA256 dengan KUNCI, lalu menempelkan hasilnya sebagai bagian ketiga — struktur yang sama dengan JWT HS256.
  2. Baris 33 (hmac.compare_digest): perbandingan tanda tangan memakai fungsi khusus yang lamanya konstan, bukan == biasa, agar pihak luar tidak bisa menebak isi tanda tangan dari selisih waktu jawaban.
  3. Baris 36-37: klaim exp (waktu kedaluwarsa) diperiksa terhadap WAKTU_SEKARANG; token lama ditolak meskipun tanda tangannya sah.
  4. Baris 41-47 (cek_akses): urutan keputusan dua tahap — tanda tangan dan masa berlaku lebih dahulu (authentication), lalu keanggotaan scope (authorization) — menghasilkan kode 401 atau 403 yang berbeda.
  5. Baris 62-68 (token palsu): payload diganti menjadi pengguna-palsu dengan scope menghapus pesanan, tetapi tanda tangan lama tetap dipakai; hasil HMAC yang dihitung ulang tidak akan cocok, sehingga token ditolak di gerbang pertama.

Kelima baris keluaran memperlihatkan seluruh spektrum keputusan: diizinkan, ditolak karena scope, ditolak karena dipalsukan, dan ditolak karena kedaluwarsa. Perhatikan bahwa server tidak pernah “mempercayai” isi payload — semua klaim diterima hanya setelah tanda tangan cocok. Daftar kesalahan autentikasi yang paling sering muncul di industri dirangkum pada OWASP Top Ten , dan detail modul hmac tersedia di dokumentasi Python .

Scope pada contoh di atas diperiksa satu per satu sebagai string. Begitu jumlah pengguna dan layanan TokoKita bertambah, daftar scope per orang menjadi sulit dirawat — itulah masalah yang diselesaikan pola role pada Chapter Role-Based Access Control.