Jika Anda membangun API untuk perangkat DIY/IoT—misalnya layar e-ink yang menerima gambar dari web/app lalu menampilkannya di device—masalah utamanya bukan hanya bagaimana request sampai, tetapi siapa yang boleh mengirim apa, seberapa sering, dan bagaimana mencegah penyalahgunaan. Ini menjadi lebih relevan ketika proyek perangkat terbuka makin populer, termasuk tren layar e-ink open source yang sempat dibahas komunitas Linux gaming. Konteksnya menarik, tetapi fokus backend tetap sama: API publik yang mengendalikan device harus di-harden.
Hardening API untuk perangkat DIY berarti memisahkan identitas user dan device, memakai kredensial yang bisa dicabut, membatasi payload dan upload, menahan brute force serta replay, dan memastikan setiap aksi penting bisa diaudit. Artikel ini membahas desain praktis yang bisa diterapkan pada backend untuk device e-ink, panel status, kontrol relay, atau perangkat serupa yang terhubung ke web/app.
Referensi konteks: tren proyek layar e-ink DIY semacam yang dibahas di GamingOnLinux dapat meningkatkan minat developer membuat perangkat yang menerima perintah dari internet. Justru di titik itulah desain API perlu dibuat defensif sejak awal.
Threat model: pahami dulu apa yang sedang Anda lindungi
Sebelum memilih JWT, API key, atau mTLS, tentukan ancaman yang realistis. Pada perangkat DIY, ancaman paling umum biasanya bukan eksploitasi canggih, melainkan desain API yang terlalu permisif.
Aset yang perlu dilindungi
- Kontrol device: misalnya endpoint untuk mengganti tampilan e-ink, reboot, update konfigurasi, atau menjalankan aksi fisik.
- Data user: akun, preferensi, daftar device, metadata gambar, histori aksi.
- Kredensial device: API secret, sertifikat, refresh token, atau provisioning token.
- Resource backend: storage upload, bandwidth, CPU image processing, queue worker, database.
Ancaman yang paling sering terjadi
- Pencurian kredensial dari firmware, log, screenshot, atau repo yang tidak sengaja dipublikasikan.
- Replay attack: request valid ditangkap lalu dikirim ulang berkali-kali.
- Abuse upload: file terlalu besar, format palsu, decompression bomb, payload gambar yang sengaja mahal diproses.
- Rate abuse: spam update layar atau brute-force endpoint auth.
- Privilege confusion: token user dipakai seolah token device, atau sebaliknya.
- IDOR (Insecure Direct Object Reference): user A dapat mengontrol device milik user B karena otorisasi hanya memeriksa ID device dari URL.
Dari threat model ini, desain yang aman biasanya mengarah ke pemisahan jalur kontrol: user mengelola kepemilikan dan kebijakan, sedangkan device mengeksekusi perintah yang sudah diotorisasi.
Pisahkan auth user dan auth device
Kesalahan umum pada API IoT adalah memperlakukan device seperti user biasa. Padahal kebutuhan dan profil risikonya berbeda.
User authentication
User berasal dari web atau mobile app. Untuk jalur ini, pola yang masuk akal adalah:
- login interaktif via session atau access token pendek,
- refresh token yang dapat dicabut,
- MFA bila perangkat mengendalikan sesuatu yang sensitif,
- otorisasi per device ownership dan role.
Untuk API backend yang dikonsumsi aplikasi web/mobile, access token pendek lebih aman dibanding token panjang yang hidup berminggu-minggu. Jika token bocor, jendela penyalahgunaannya lebih kecil.
Device authentication
Device tidak cocok memakai alur login user. Ia butuh identitas non-interaktif yang:
- stabil untuk provisioning,
- bisa diputar (rotate),
- bisa dicabut per device,
- terpisah dari identitas user pemilik.
Pola minimal yang cukup aman untuk banyak proyek DIY:
- Device dipasangkan ke akun user melalui provisioning token sekali pakai atau kode pairing pendek.
- Setelah pairing berhasil, backend menerbitkan device credential khusus untuk device itu.
- Device memakai credential tersebut untuk mengambil perintah, upload status, atau mengunduh aset.
- Jika device hilang/kompromi, credential dapat dicabut tanpa mengganggu akun user.
Yang penting: jangan simpan secret global yang sama di semua device. Satu kebocoran akan mengompromikan seluruh armada perangkat.
Kapan memakai API key, JWT, atau mTLS?
Tidak ada satu jawaban untuk semua arsitektur. Pilih berdasarkan kemampuan device, risiko operasional, dan kebutuhan pencabutan akses.
API key
Cocok untuk: device sederhana, prototipe serius, atau internal deployment dengan backend yang Anda kendalikan penuh.
Kelebihan:
- Sederhana diimplementasikan.
- Mudah dicabut per device.
- Tidak butuh parsing token kompleks di sisi device.
Kekurangan:
- Jika key panjang umur dan bocor, attacker dapat terus mengakses API sampai key dicabut.
- Perlu mekanisme anti-replay tambahan.
- Sering disalahgunakan sebagai identitas sekaligus otorisasi, padahal tetap perlu scope/policy.
Praktik yang baik: simpan hanya hash dari API key di server, tampilkan nilai rahasia penuh hanya saat provisioning, dan beri prefix/ID publik agar pencarian di database tidak bergantung pada plaintext key.
JWT atau access token pendek
Cocok untuk: user session dan device yang perlu token berumur pendek setelah bootstrap awal.
Kelebihan:
- Masa berlaku bisa pendek.
- Bisa membawa klaim seperti subject, scope, device_id, atau owner_id.
- Cocok untuk arsitektur terdistribusi.
Kekurangan:
- Revocation tidak sesederhana session server-side jika token dibiarkan stateless.
- Mudah jadi terlalu kompleks jika semua masalah dipaksa diselesaikan dengan JWT.
- Token terlalu gemuk atau terlalu lama masa berlakunya sering menjadi sumber risiko.
Rekomendasi praktis: gunakan JWT/access token pendek untuk user, dan untuk device gunakan hanya jika Anda sudah menyiapkan alur rotasi, expiry pendek, dan refresh/reauth yang rapi.
mTLS
Cocok untuk: device yang lebih mampu, deployment industrial/internal, atau ketika Anda benar-benar perlu identitas kriptografis kuat antar mesin.
Kelebihan:
- Mutual authentication di level transport.
- Sulit disalahgunakan tanpa private key dan sertifikat yang benar.
- Bagus untuk komunikasi device ke gateway/backend dalam jaringan yang lebih terkendali.
Kekurangan:
- Provisioning dan rotasi sertifikat lebih kompleks.
- Debugging lebih sulit dibanding API key biasa.
- Kurang praktis untuk banyak proyek DIY kecil jika tim belum siap mengelola PKI.
Aturan cepat:
- API key untuk device sederhana, asal per-device dan bisa dicabut.
- JWT/access token pendek untuk user, dan untuk device jika lifecycle token dikelola serius.
- mTLS jika identitas mesin-ke-mesin adalah prioritas utama dan kompleksitas operasional dapat ditanggung.
Desain request yang aman: token pendek, rotasi secret, dan anti-replay
Transport harus selalu memakai HTTPS. Namun TLS saja tidak mencegah replay jika request yang sudah valid disalin dan dikirim ulang. Untuk endpoint kontrol device, tambahkan kontrol di level aplikasi.
Pola yang disarankan
- Access token pendek untuk user dan/atau device.
- Refresh atau re-provision untuk mendapatkan token baru.
- Timestamp + nonce pada request sensitif.
- Signature/HMAC jika memakai API secret statis atau semi-statis.
- Idempotency key untuk operasi yang bisa terduplikasi karena retry.
Contoh header request device
POST /v1/device/commands/ack HTTP/1.1
Host: api.example.local
Authorization: Bearer <access_token_pendek>
X-Device-Id: dev_01H...
X-Timestamp: 1751546400
X-Nonce: 6f0d4c0b-0d7d-4f7a-8e2f-...
X-Signature: base64(hmac_sha256(secret, method + path + body_hash + timestamp + nonce))
Content-Type: application/json
{"command_id":"cmd_123","status":"applied"}Server lalu memverifikasi beberapa hal:
- token valid, belum kedaluwarsa, dan memang milik device tersebut,
X-Timestampmasih dalam jendela waktu wajar,X-Noncebelum pernah dipakai untuk device itu dalam periode tertentu,- signature cocok dengan body dan metadata request,
- scope token mengizinkan endpoint tersebut.
Mengapa pendekatan ini bekerja? Karena attacker tidak cukup hanya memiliki salinan request lama. Jika nonce sudah pernah dipakai atau timestamp sudah lewat jendela validitas, replay ditolak.
Contoh pseudocode verifikasi anti-replay
function verifySignedRequest(req, deviceSecret) {
assert(abs(nowUnix() - req.timestamp) <= 300)
const nonceKey = `nonce:${req.deviceId}:${req.nonce}`
if (cache.exists(nonceKey)) {
throw Unauthorized("replay detected")
}
const bodyHash = sha256(req.rawBody)
const canonical = [req.method, req.path, bodyHash, req.timestamp, req.nonce].join("\n")
const expected = hmacSha256Base64(deviceSecret, canonical)
if (!constantTimeEqual(expected, req.signature)) {
throw Unauthorized("invalid signature")
}
cache.set(nonceKey, "1", ttl=300)
}Detail implementasi bisa berbeda, tetapi prinsipnya tetap:
- gunakan raw body atau representasi kanonik yang konsisten saat menandatangani,
- bandingkan signature dengan constant-time comparison,
- simpan nonce pada cache cepat seperti Redis dengan TTL sesuai jendela replay.
Rotasi secret tanpa memutus device
Rotasi secret sering diabaikan sampai insiden terjadi. Minimal, server harus mendukung dua secret aktif sementara:
- current_secret
- next_secret atau previous_secret untuk masa transisi
Saat device mengambil konfigurasi baru atau melakukan handshake, backend bisa menerbitkan secret baru dengan masa berlaku transisi. Selama periode itu, request yang ditandatangani secret lama dan baru sama-sama diterima. Setelah cutover selesai, secret lama dicabut.
Jangan lupa beri kemampuan untuk:
- revoke credential per device,
- memaksa re-pairing bila kompromi berat,
- mencatat kapan secret terakhir dipakai.
Validasi payload dan upload aset/gambar yang aman
Pada device e-ink atau panel sejenis, jalur upload sering menjadi permukaan serangan terbesar. Walaupun aplikasi Anda hanya menerima gambar, anggap semua file sebagai input tak tepercaya.
Aturan dasar validasi payload
- Terapkan schema validation pada JSON: tipe data, batas panjang string, enum, range angka, field wajib, dan penolakan field tak dikenal bila perlu.
- Validasi otorisasi berbasis resource: apakah user/token ini benar-benar boleh mengubah
device_idtersebut? - Batasi ukuran body sejak di reverse proxy dan aplikasi.
- Gunakan content-type hanya sebagai petunjuk, bukan satu-satunya validasi.
Upload gambar: apa yang perlu dibatasi?
- Ukuran file: batasi maksimal byte upload.
- Dimensi gambar: tolak resolusi tidak wajar walaupun ukuran file kecil.
- Format file: verifikasi signature/magic bytes, bukan hanya ekstensi.
- Jumlah frame/kompleksitas: file tertentu bisa mahal diproses walaupun tampak normal.
- Nama file: jangan percaya nama asli untuk path atau object key.
Untuk device yang hanya butuh bitmap akhir, backend sebaiknya melakukan normalisasi:
- terima file dari user,
- simpan sementara di lokasi karantina,
- verifikasi ukuran, format, dan dimensi,
- decode dengan library yang tepercaya,
- re-encode ke format internal yang aman dan sederhana,
- baru distribusikan hasil final ke device.
Dengan cara ini, device tidak perlu memproses file kompleks langsung dari user. Backend mengambil beban validasi dan sanitasi.
Contoh aturan upload yang realistis
POST /v1/assets
- Auth: user token
- Max body size: 8 MB
- Allowed formats: PNG, JPEG
- Verify magic bytes, not just extension
- Reject images above configured pixel limit
- Store original in quarantine bucket
- Run image decode + re-encode worker
- Produce normalized asset + metadata
- Only normalized asset can be attached to device commandJika Anda menggunakan object storage, pertimbangkan pola berikut:
- Pre-signed upload URL hanya untuk user yang sudah diautentikasi.
- Upload masuk ke bucket/namespace karantina.
- Worker memindahkan hasil bersih ke bucket final.
- Device hanya boleh mengunduh aset final, bukan file mentah user.
Kesalahan umum pada upload
- Mengizinkan device mengunduh langsung URL file mentah dari user.
- Hanya memeriksa ekstensi
.pngatau.jpg. - Tidak membatasi dimensi, sehingga file kecil tetapi resolusinya sangat besar.
- Memproses gambar sinkron di request utama hingga endpoint mudah di-DoS.
- Menyimpan file dengan path yang berasal dari input user.
Rate limit per device/IP dan pencegahan abuse
Rate limit bukan hanya untuk melindungi API dari traffic tinggi, tetapi untuk membatasi biaya komputasi, menjaga stabilitas queue, dan menahan perilaku salah dari device yang bug atau attacker yang mencoba menebak kredensial.
Jangan hanya rate limit per IP
Pada IoT, banyak device bisa berada di belakang NAT yang sama. Jika hanya rate limit per IP, Anda bisa memblokir pengguna sah. Sebaliknya, attacker juga bisa berpindah IP. Karena itu, gabungkan beberapa dimensi:
- per IP untuk endpoint publik dan auth,
- per device_id untuk jalur device,
- per user_id untuk operasi manajemen,
- per endpoint class untuk operasi mahal seperti upload atau image rendering.
Contoh kebijakan yang masuk akal
- Login/pairing: agresif per IP dan per account/device identifier.
- Upload aset: ketat per user dan per IP, plus concurrency limit.
- Polling device: long-polling atau interval minimum agar tidak hammering backend.
- Kontrol update layar: batasi frekuensi per device untuk mencegah spam dan keausan resource backend.
Alih-alih hanya membalas 429, pertimbangkan juga:
- cooldown sementara untuk device yang berperilaku anomali,
- backoff instructions di respons agar klien tahu kapan retry,
- queue deduplication untuk perintah identik yang datang berulang.
Contoh pseudocode rate limit gabungan
keys = [
`rl:ip:${ip}:login`,
`rl:user:${userId}:upload`,
`rl:device:${deviceId}:command`
]
for each key in applicableKeys(route):
if counter(key, window).increment() > limit(key):
reject 429Untuk operasi mahal, token bucket atau sliding window umumnya lebih berguna daripada counter kasar per menit. Yang penting, definisikan limit berdasarkan karakter endpoint, bukan satu angka global untuk semua route.
Audit log, observability, dan forensik minimum
Jika API mengendalikan device fisik atau layar yang terlihat pengguna, Anda perlu tahu siapa melakukan apa dan kapan. Audit log bukan sekadar debug; ia penting untuk incident response.
Apa yang perlu dicatat?
- Identity: user_id, device_id, client_id, atau subject token.
- Aksi: login, pairing, rotate secret, upload asset, assign asset to device, execute command, revoke device.
- Hasil: sukses, ditolak, expired, rate-limited, replay-detected.
- Konteks: IP, user-agent, request ID, correlation ID, timestamp.
Hindari menulis secret, token mentah, atau file sensitif ke log. Jika perlu korelasi, simpan identifier aman seperti key prefix, token ID, atau hash terpotong.
Log yang membantu debugging insiden
- berapa kali device gagal verifikasi signature,
- nonce replay yang terdeteksi per device,
- lonjakan upload dari user tertentu,
- device yang polling terlalu sering,
- secret lama yang masih dipakai setelah masa rotasi.
Tambahkan metrik dan alert untuk pola-pola tersebut. Pada banyak kasus, bug firmware terlihat sama seperti serangan sampai Anda punya telemetry yang cukup.
Contoh alur request aman dari web ke device
Berikut alur yang relatif aman untuk kasus user mengunggah gambar lalu menampilkan ke layar e-ink device.
- User login ke web/app dan mendapat session atau access token pendek.
- User upload gambar ke endpoint aset atau pre-signed URL karantina.
- Backend memvalidasi dan menormalisasi gambar melalui worker.
- User membuat command untuk device tertentu memakai asset hasil normalisasi.
- Backend memeriksa ownership dan policy: apakah user berhak mengontrol device itu?
- Command disimpan dengan ID unik dan status pending.
- Device melakukan poll atau menerima push dengan credential device miliknya.
- Backend mengirim metadata command dan URL aset final yang terbatas masa berlaku atau melalui endpoint terproteksi.
- Device mengunduh aset final, menerapkan command, lalu mengirim ACK dengan timestamp, nonce, dan signature.
- Backend mencatat audit log untuk seluruh rangkaian aksi.
Pemisahan ini penting karena user tidak berbicara langsung ke device dengan kredensial device. Semua kontrol melalui backend yang menegakkan kebijakan.
Kesalahan umum yang sering membuat API perangkat DIY rapuh
- Satu secret untuk semua device.
- Token terlalu panjang umurnya tanpa mekanisme revoke yang jelas.
- Tidak memisahkan identitas user dan device.
- Otorisasi hanya berdasarkan device_id di URL tanpa cek kepemilikan.
- Upload diteruskan mentah ke device.
- Tidak ada proteksi replay pada endpoint sensitif.
- Rate limit hanya per IP.
- Log menyimpan secret atau token mentah.
- Firmware/device terus retry tanpa backoff saat backend error.
Banyak masalah di atas tidak terlihat saat sistem masih kecil. Begitu API dipublikasikan atau perangkat mulai digunakan orang lain, celah tersebut cepat menjadi insiden nyata.
Checklist implementasi hardening API untuk perangkat DIY
- Gunakan HTTPS di semua jalur publik.
- Pisahkan auth user dan auth device.
- Setiap device memiliki credential unik yang bisa dicabut.
- Pakai access token pendek untuk user; hindari token panjang tanpa revocation.
- Tambahkan timestamp + nonce untuk endpoint device yang sensitif.
- Verifikasi signature/HMAC dengan constant-time comparison bila relevan.
- Sediakan rotasi secret dengan masa transisi.
- Terapkan schema validation dan batas ukuran body.
- Untuk upload gambar, verifikasi magic bytes, ukuran, dimensi, lalu re-encode.
- Gunakan bucket/namespace karantina untuk file mentah.
- Terapkan rate limit per IP, user, device, dan kelas endpoint.
- Tambahkan backoff dan deduplikasi command/retry.
- Catat audit log untuk pairing, revoke, upload, command, dan ACK.
- Jangan log secret, token mentah, atau URL sensitif.
- Uji skenario replay, duplicate upload, brute force, dan ownership bypass.
Penutup
Hardening API untuk perangkat DIY bukan berarti membuat sistem berlebihan, melainkan memastikan komponen paling berisiko dipagari dengan kontrol yang tepat. Untuk kebanyakan backend IoT kecil hingga menengah, kombinasi yang paling pragmatis adalah: auth user terpisah dari auth device, token pendek, credential per-device yang bisa dirotasi, validasi upload yang ketat, anti-replay untuk endpoint sensitif, serta rate limit multi-dimensi.
Jika Anda baru mulai, jangan kejar semua fitur keamanan sekaligus. Mulailah dari threat model, pisahkan identity domain, tutup jalur upload, lalu tambahkan audit dan replay protection. Fondasi itu biasanya memberi peningkatan keamanan terbesar tanpa mengunci arsitektur Anda terlalu dini.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!