Saat tim mulai menulis ulang komponen inti ke Rust—misalnya dalam konteks tren seperti pgrust—keamanan backend tidak otomatis selesai hanya karena bahasanya lebih aman terhadap banyak kelas bug memori. Checklist hardening auth & session untuk backend Rust tetap perlu dibangun secara eksplisit: bagaimana session disimpan, bagaimana secret dirotasi, bagaimana password di-hash, bagaimana input dan upload dibatasi, serta bagaimana abuse dideteksi dan diuji.

Jika Anda sedang membangun service/API Rust, fokus utama seharusnya bukan sekadar “pakai Rust”, tetapi memastikan alur autentikasi dan state sesi tahan terhadap pencurian token, brute force, replay, credential stuffing, upload berbahaya, dan kesalahan konfigurasi. Artikel ini disusun sebagai checklist implementasi yang bisa dipakai sebelum rilis dan saat security review.

1. Tetapkan model ancaman sebelum memilih auth dan session

Sebelum memilih cookie session atau token stateless, tentukan ancaman yang benar-benar relevan:

  • Session hijacking: attacker mencuri cookie atau bearer token dari browser, log, proxy, atau perangkat yang terkompromi.
  • CSRF: request sah dari browser korban dikirim ke API karena cookie otomatis ikut terkirim.
  • XSS: script berbahaya mencuri token yang disimpan di tempat yang bisa diakses JavaScript.
  • Credential stuffing: login dicoba massal memakai kombinasi email/password hasil kebocoran.
  • Replay: token atau refresh token dipakai ulang setelah seharusnya ditolak.
  • Privilege abuse: session lama tetap aktif setelah password diganti atau role berubah.

Rust membantu mengurangi bug memory safety, tetapi ancaman di atas berada di level desain aplikasi dan operasional. Karena itu, checklist hardening harus dimulai dari model ancaman, bukan dari pilihan crate.

2. Pilih desain session: cookie server-side vs token stateless

2.1 Kapan memilih session cookie

Untuk aplikasi web dengan browser sebagai klien utama, session cookie server-side biasanya lebih sederhana dan lebih mudah dicabut. Server menyimpan state session di database atau store seperti Redis, sementara browser hanya menyimpan identifier acak.

Kelebihan:

  • Revocation lebih mudah: hapus session di store, selesai.
  • Rotasi dan invalidasi global lebih sederhana.
  • Payload sensitif tidak perlu disimpan di klien.

Trade-off:

  • Perlu session store yang andal.
  • Harus menangani CSRF jika cookie dipakai lintas request browser.
  • Butuh strategi scaling dan TTL yang jelas.

2.2 Kapan memilih bearer token

Untuk API yang diakses mobile app, service-to-service, atau klien non-browser, bearer token bisa lebih cocok. Namun, makin stateless sebuah token, makin sulit pencabutannya secara real-time.

Kelebihan:

  • Mudah dipakai oleh klien non-browser.
  • Tidak tergantung cookie otomatis dari browser.
  • Cocok untuk edge/service terdistribusi.

Trade-off:

  • Revocation lebih sulit jika token benar-benar stateless.
  • Jika token bocor, attacker bisa menggunakannya sampai masa berlaku habis.
  • Refresh flow perlu dirancang hati-hati agar tidak membuka replay.

2.3 Aturan praktis pemilihan

  • Gunakan cookie session untuk web app berbasis browser ketika Anda ingin kontrol revocation yang kuat.
  • Gunakan access token pendek + refresh token untuk klien non-browser atau integrasi API.
  • Jika memakai token pada browser, hindari menyimpan token di tempat yang mudah diakses JavaScript bila tidak benar-benar perlu.

Kesalahan umum: memilih JWT hanya karena terasa modern, padahal kebutuhan utamanya adalah session biasa yang mudah dicabut. Stateless bukan otomatis lebih aman.

3. Checklist hardening session dan cookie

3.1 Gunakan session ID acak dengan entropi tinggi

Session ID harus dihasilkan dari CSPRNG, bukan timestamp, counter, atau pola yang dapat ditebak. Jangan memasukkan makna bisnis ke dalam ID session.

  • Panjang ID cukup besar untuk mencegah tebakan brute force.
  • Simpan hanya representasi yang diperlukan di store.
  • Pertimbangkan hashing session ID di database agar kebocoran store tidak langsung setara dengan takeover session.

3.2 Set atribut cookie dengan ketat

Jika memakai cookie:

  • HttpOnly: mencegah akses dari JavaScript.
  • Secure: hanya terkirim via HTTPS.
  • SameSite: pilih Lax atau Strict bila memungkinkan untuk mengurangi CSRF.
  • Path dan Domain: jangan terlalu luas.
  • Max-Age/Expires: sesuaikan dengan TTL session.

Trade-off SameSite: nilai terlalu ketat dapat memutus alur login tertentu, redirect lintas domain, atau integrasi pihak ketiga. Uji alur nyata, bukan hanya login lokal.

3.3 Rotasi session setelah peristiwa sensitif

Lakukan regenerasi session ID setelah:

  • Login berhasil
  • Perubahan password
  • Aktivasi MFA
  • Peningkatan privilege atau role

Tujuannya mencegah session fixation dan memastikan state lama tidak dipakai terus.

3.4 Batasi umur session

  • Idle timeout: session mati jika tidak aktif dalam jangka waktu tertentu.
  • Absolute timeout: session tetap mati walaupun aktif terus.
  • Remember me: pisahkan dari session biasa dan beri TTL, scope, serta revocation yang berbeda.

3.5 Kaitkan session dengan konteks secara hati-hati

Beberapa tim mengikat session ke IP atau fingerprint. Ini bisa membantu mendeteksi anomali, tetapi terlalu agresif akan memutus pengguna yang berpindah jaringan seluler, VPN, atau NAT perusahaan.

Pendekatan yang lebih realistis:

  • Simpan metadata seperti IP awal, user-agent, geolokasi kasar, dan waktu akses terakhir.
  • Gunakan untuk risk scoring dan alert, bukan pemblokiran keras di semua kasus.
  • Minta re-auth untuk aktivitas sensitif bila sinyal risiko naik.

3.6 Contoh struktur session server-side

session_id -> {
  user_id,
  issued_at,
  last_seen_at,
  expires_at,
  auth_level,
  mfa_verified,
  csrf_secret,
  ip_hash,
  user_agent_hash,
  revoked_at
}

Struktur ini bukan format baku, tetapi menunjukkan bahwa session sebaiknya menyimpan konteks minimum yang berguna untuk enforcement dan audit.

4. Checklist hardening token, refresh token, dan revocation

4.1 Buat access token berumur pendek

Jika memakai bearer token, access token sebaiknya berumur pendek agar dampak kebocoran berkurang. Jangan menaruh data sensitif yang tidak perlu ke dalam token yang akan dikirim ke banyak komponen.

4.2 Simpan refresh token secara lebih ketat

  • Anggap refresh token setara atau lebih sensitif daripada password sementara.
  • Simpan refresh token dalam bentuk hash di server jika arsitektur Anda memungkinkan.
  • Dukung rotasi refresh token: token lama tidak boleh tetap valid tanpa batas.

4.3 Terapkan token family dan deteksi replay

Saat refresh token dipakai, keluarkan token baru dan invalidasi token lama. Jika token lama dipakai lagi, anggap sebagai indikasi replay dan cabut seluruh keluarga token yang terkait akun/perangkat tersebut.

Ini penting karena kebocoran refresh token sering baru terdeteksi setelah attacker mulai memakainya paralel dengan klien sah.

4.4 Sediakan revocation yang nyata

Jika bisnis Anda butuh logout dari semua perangkat, penonaktifan akun, atau pencabutan akses cepat setelah insiden, jangan bergantung pada token stateless murni tanpa lapisan revocation. Gunakan salah satu:

  • Store revocation / denylist dengan TTL
  • Versioning token per-user
  • Session store terpusat untuk refresh token

Kesalahan umum: token memiliki masa berlaku panjang, refresh token tidak dirotasi, lalu tim berasumsi “logout” cukup dengan menghapus token di klien. Itu bukan revocation yang andal.

5. Password hashing: jangan simpan password, jangan hash cepat

5.1 Gunakan password hashing yang memang untuk password

Jangan gunakan hash cepat umum seperti SHA-256 langsung untuk password pengguna. Gunakan algoritme yang memang dirancang untuk password hashing, seperti Argon2, scrypt, atau bcrypt, dengan parameter kerja yang ditinjau sesuai kapasitas server dan target keamanan.

Dalam banyak sistem modern, Argon2id sering menjadi pilihan yang baik karena memberi resistensi terhadap serangan berbasis GPU lebih baik dibanding hash cepat.

5.2 Simpan salt unik per password

Salt unik per password mencegah rainbow table dan membuat dua password sama tidak menghasilkan hash yang sama. Biasanya pustaka password hashing sudah menangani format penyimpanan salt bersama hash.

5.3 Pertimbangkan pepper di luar database

Pepper adalah secret tambahan yang tidak disimpan di database aplikasi. Ini berguna sebagai lapisan ekstra jika database bocor, tetapi aplikasi dan secret manager tidak.

Trade-off:

  • Rotasi pepper lebih kompleks.
  • Jika pepper hilang, verifikasi password bisa gagal total.
  • Tidak menggantikan password hashing yang kuat.

5.4 Upgrade hash secara bertahap

Saat parameter hashing berubah, lakukan rehash on login: setelah password diverifikasi, simpan ulang dengan parameter baru. Ini cara praktis meningkatkan keamanan tanpa migrasi massal yang berisiko.

5.5 Checklist password flow

  • Hash password dengan algoritme khusus password.
  • Bandingkan hash dengan operasi waktu konstan bila relevan.
  • Jangan log password, OTP, reset token, atau hash mentah.
  • Password reset token harus sekali pakai, acak, dan berumur pendek.
  • Cabut session aktif setelah reset password atau beri opsi “logout semua perangkat”.

6. Secret handling: env cukup untuk dev, manager untuk produksi

6.1 Pisahkan jenis secret

Jangan perlakukan semua secret sama. Klasifikasikan minimal menjadi:

  • App secret untuk signing/encryption
  • Credential database
  • API key pihak ketiga
  • Key untuk token signing
  • Pepper password atau secret CSRF

Setiap jenis secret punya kebutuhan rotasi dan dampak insiden yang berbeda.

6.2 Penyimpanan secret

Untuk pengembangan lokal, environment variable sering cukup. Untuk produksi, lebih aman menggunakan secret manager atau mekanisme setara yang mendukung kontrol akses, audit, dan rotasi.

Praktik minimum:

  • Jangan commit secret ke repository.
  • Jangan bake secret permanen ke image container.
  • Batasi siapa yang bisa membaca secret pada runtime.
  • Masking secret di log, panic report, dan error telemetry.

6.3 Rancang rotasi secret dari awal

Rotasi sering gagal bukan karena teknis kriptografi, tetapi karena aplikasi hanya mendukung satu secret aktif. Untuk signing token atau cookie, dukung key ring: satu key aktif untuk menandatangani, beberapa key lama masih diterima selama masa transisi.

struct KeyRing {
    active_kid: String,
    signing_keys: Vec<SigningKey>,
}

// Verifikasi: coba berdasarkan kid, atau fallback terkontrol
// Signing: selalu pakai active_kid

Dengan pola ini, Anda bisa merotasi key tanpa memaksa logout semua pengguna secara mendadak, kecuali memang itu keputusan insiden.

6.4 Common mistake pada secret

  • Menggunakan satu secret untuk banyak fungsi berbeda.
  • Tidak memberi identitas key versi (key id).
  • Tidak punya prosedur darurat jika key bocor.
  • Menaruh secret di file konfigurasi yang ikut terbaca endpoint debug atau support bundle.

7. Validasi input berlapis: parse, validate, normalize, authorize

Rust membantu dengan tipe data, tetapi input validation tetap harus berlapis. Jangan berhenti pada deserialisasi JSON yang berhasil.

7.1 Lapisan validasi yang disarankan

  1. Transport-level: batasi ukuran body request.
  2. Schema-level: pastikan field wajib, tipe, enum, panjang string, dan format dasar.
  3. Business-level: cek aturan domain, misalnya tanggal tidak mundur, role tidak boleh dinaikkan sendiri.
  4. Authorization-level: user boleh melakukan aksi ini atau tidak.
  5. Persistence-level: constraint database tetap aktif sebagai pagar terakhir.

7.2 Normalize sebelum dipakai

Normalisasi membantu mencegah bypass dan duplikasi logika:

  • Trim spasi bila sesuai konteks.
  • Canonicalize email jika aturan bisnis mendukung.
  • Batasi charset untuk identifier tertentu.
  • Jangan melakukan normalisasi yang mengubah makna data tanpa aturan jelas.

7.3 Hindari asumsi “ORM sudah aman untuk semuanya”

Parameter binding memang mengurangi risiko injection SQL, tetapi bukan berarti semua aman. Anda masih perlu:

  • Memvalidasi field sort/filter dinamis.
  • Mencegah path traversal untuk input file/path.
  • Memastikan output yang kembali ke HTML atau template di-escape sesuai konteks.

7.4 Contoh DTO dan validasi awal

#[derive(serde::Deserialize)]
struct LoginRequest {
    email: String,
    password: String,
}

fn validate_login(req: &LoginRequest) -> Result<(), String> {
    if req.email.len() > 254 {
        return Err("email terlalu panjang".into());
    }
    if req.password.is_empty() || req.password.len() > 1024 {
        return Err("password tidak valid".into());
    }
    Ok(())
}

Contoh ini sengaja sederhana. Intinya, lakukan validasi eksplisit untuk panjang, karakter, dan batasan bisnis; jangan hanya mengandalkan tipe String.

8. Upload hardening: batasi ukuran, tipe, dan jalur pemrosesan

8.1 Tetapkan batas upload dari beberapa sisi

  • Batas ukuran body di reverse proxy/gateway.
  • Batas ukuran multipart di aplikasi Rust.
  • Batas ukuran per file dan total per request.
  • Batas jumlah file dan field multipart.

Tujuannya mencegah memory exhaustion, disk exhaustion, dan pemrosesan file yang terlalu mahal.

8.2 Jangan percaya MIME type dari klien

Header Content-Type dari klien hanya petunjuk. Verifikasi tipe file dengan inspeksi konten bila diperlukan, dan whitelist tipe yang memang didukung bisnis.

8.3 Simpan file di lokasi non-eksekusi

  • Jangan simpan upload di direktori yang bisa dieksekusi server.
  • Gunakan nama file acak, bukan nama dari pengguna.
  • Jangan gunakan path yang dibentuk langsung dari input klien.
  • Pertimbangkan pipeline scanning terpisah untuk file berisiko tinggi.

8.4 Gunakan pemrosesan streaming bila memungkinkan

Untuk file besar, hindari membaca semua ke memori. Streaming mengurangi risiko OOM dan lebih cocok untuk service produksi. Walau detail implementasinya bergantung pada framework Rust yang dipakai, prinsipnya sama: proses bertahap, ukur, dan fail fast jika melebihi limit.

8.5 Ancaman umum pada upload

  • Zip bomb atau arsip berlapis yang meledakkan ukuran saat diekstrak.
  • Polyglot file yang lolos pemeriksaan dangkal.
  • Path traversal pada nama file atau arsip.
  • Parser exploit pada library pemrosesan gambar, PDF, atau dokumen.

Jika aplikasi tidak benar-benar perlu menerima banyak jenis file, whitelist yang sempit hampir selalu lebih aman daripada blacklist.

9. Rate limit dan abuse prevention: jangan hanya per IP

9.1 Pisahkan jenis endpoint

Endpoint login, password reset, verifikasi OTP, dan upload tidak boleh memakai limit yang sama dengan endpoint baca biasa. Setiap endpoint sensitif perlu kebijakan sendiri.

9.2 Gunakan beberapa dimensi pembatasan

  • Per IP: berguna untuk noise umum, tetapi lemah di balik NAT atau botnet.
  • Per user/account: penting untuk mencegah brute force pada satu akun.
  • Per device/session: membantu membatasi abuse dari klien yang sama.
  • Per route: upload, login, dan reset password butuh ambang berbeda.

9.3 Terapkan progressive friction

Jangan langsung memblokir permanen. Tambahkan hambatan bertingkat:

  • Penundaan respons yang terukur
  • Captcha atau challenge tambahan jika relevan
  • Step-up auth / MFA untuk aktivitas sensitif
  • Temporary lock dengan audit yang jelas

Hati-hati: lockout yang terlalu mudah bisa dipakai attacker untuk denial of service terhadap akun korban.

9.4 Simpan state rate limit di store yang tepat

Untuk deployment multi-instance, rate limit harus memakai store bersama agar konsisten. Redis sering dipakai untuk ini, tetapi prinsipnya bukan pada produknya: Anda butuh operasi atomik, TTL, dan observabilitas.

9.5 Login flow yang lebih tahan abuse

  • Respons error login dibuat konsisten agar tidak memudahkan enumerasi akun.
  • Batasi percobaan login per IP dan per akun.
  • Audit percobaan gagal dan sukses dari lokasi/perangkat baru.
  • Terapkan MFA untuk akun berisiko tinggi atau admin.

10. Audit logging: cukup detail untuk investigasi, tidak membocorkan data

10.1 Event yang wajib dicatat

  • Login sukses/gagal
  • Logout
  • Password reset diminta/berhasil
  • Perubahan email, password, role, MFA
  • Pembuatan dan pencabutan session/token
  • Rate limit hit, blokir sementara, anomali akses
  • Upload ditolak karena tipe/ukuran

10.2 Isi log yang berguna

  • Timestamp
  • User ID atau account ID bila ada
  • Request ID / trace ID
  • IP dan user-agent secara terukur
  • Hasil aksi dan alasan penolakan
  • Metadata risiko, misalnya login dari perangkat baru

10.3 Jangan log data sensitif mentah

Hindari mencatat password, token bearer, refresh token, cookie session, OTP, atau secret internal. Jika perlu korelasi, log hash atau identifier parsial yang aman.

Di Rust, ini juga berarti Anda perlu berhati-hati pada Debug output struct request/response. Banyak kebocoran terjadi karena struct sensitif dicetak utuh saat error.

11. Uji regresi keamanan untuk auth dan session

Checklist hardening tidak lengkap tanpa pengujian berulang. Minimal, tambahkan uji regresi untuk skenario berikut:

11.1 Test case yang sebaiknya otomatis

  • Session ID berubah setelah login.
  • Session lama tidak valid setelah logout atau revoke.
  • Password reset token hanya bisa dipakai sekali.
  • Refresh token lama ditolak setelah rotasi.
  • Cookie sensitif selalu punya HttpOnly dan Secure.
  • Request melebihi body limit ditolak lebih awal.
  • Upload dengan tipe/ukuran salah ditolak.
  • Rate limit aktif pada endpoint login/reset/upload.
  • Role change memaksa re-auth atau invalidasi session sesuai kebijakan.

11.2 Tambahkan test negatif

Jangan hanya menguji jalur sukses. Tambahkan skenario penyalahgunaan:

  • Replay refresh token
  • CSRF pada endpoint berbasis cookie
  • Input dengan field ekstra, string sangat panjang, atau format rusak
  • Multipart dengan boundary aneh atau jumlah part berlebihan
  • Brute force terdistribusi yang lolos limit per-IP tunggal

11.3 Uji observabilitas dan prosedur insiden

Pastikan bukan hanya aplikasi yang menolak serangan, tetapi tim juga bisa melihatnya:

  • Apakah event rate limit muncul di dashboard?
  • Apakah revoke semua session bisa dilakukan cepat?
  • Apakah rotasi key bisa dilakukan tanpa deploy darurat yang berisiko?

12. Checklist review sebelum rilis

Gunakan daftar singkat ini saat final review service/API Rust:

  • Model ancaman auth dan session sudah ditulis, bukan asumsi lisan.
  • Pemilihan cookie vs token sesuai jenis klien dan kebutuhan revocation.
  • Cookie sensitif memakai HttpOnly, Secure, dan SameSite yang tepat.
  • Session diregenerasi setelah login dan perubahan status sensitif.
  • TTL idle dan absolute timeout sudah jelas.
  • Access token pendek; refresh token dirotasi dan bisa dicabut.
  • Password di-hash dengan algoritme khusus password, bukan hash cepat.
  • Secret tidak ada di repo/image; produksi memakai mekanisme penyimpanan yang layak.
  • Key rotation didukung dengan versi key atau key ring.
  • Ukuran body request, multipart, dan upload dibatasi di beberapa lapisan.
  • Validasi input mencakup schema, bisnis, otorisasi, dan constraint database.
  • Rate limit diterapkan per IP, per user, dan per route sensitif.
  • Audit log ada untuk event auth utama tanpa membocorkan secret.
  • Test regresi keamanan berjalan di CI untuk flow auth, session, reset, upload, dan abuse.

Penutup

Konteks seperti pgrust mengingatkan kita bahwa menulis ulang sistem inti ke Rust adalah kesempatan bagus untuk meninjau ulang keamanan backend, bukan alasan untuk menganggap hardening selesai. Checklist hardening auth & session untuk backend Rust harus mencakup desain session, pengelolaan secret, validasi input, upload, rate limit, logging, dan pengujian regresi secara terpadu.

Jika Anda harus memulai dari yang paling berdampak, urutannya biasanya sederhana: pilih model session yang tepat, perketat cookie/token dan revocation, pastikan password hashing benar, rapikan secret handling, pasang limit pada request/upload, lalu tambahkan audit logging serta test regresi keamanan. Itu jauh lebih bernilai daripada sekadar mengganti bahasa tanpa memperbaiki desain lapisan auth.