ID terstruktur untuk session dan token tidak otomatis aman maupun berbahaya; jawabannya bergantung pada apakah nilai tersebut hanya dipakai sebagai identifier internal database atau juga diekspos ke klien sebagai token publik. Jika sebuah ID memuat informasi seperti waktu, tenant, region, atau urutan, maka ia bisa sangat berguna untuk indexing, audit, dan operasi internal. Namun saat nilai yang sama dijadikan session ID, refresh token, atau upload token yang diterima dari user, risikonya berubah: prediktabilitas, enumerasi, dan kebocoran metadata menjadi masalah nyata.

Prinsip praktisnya sederhana: structured primary keys cocok untuk kebutuhan internal, sedangkan token publik sebaiknya opaque, sulit ditebak, memiliki expiry, divalidasi ketat, dibatasi dengan rate limit, dan idealnya disimpan dalam bentuk hash. Pemisahan ini mengurangi risiko tanpa mengorbankan kebutuhan operasional seperti audit log, sharding, atau analisis abuse.

Apa yang dimaksud ID terstruktur?

ID terstruktur adalah identifier yang membawa pola atau makna tertentu, bukan sekadar bilangan acak. Contohnya:

  • prefix tenant atau region, misalnya eu_ atau tenant_42
  • komponen waktu, misalnya timestamp yang terenkode
  • komponen urutan, misalnya counter atau sequence
  • gabungan beberapa atribut, misalnya region-timestamp-seq

Di level database, pola seperti ini sering dipakai karena membantu:

  • lokalitas index dan performa insert
  • partisi data berdasarkan waktu atau tenant
  • routing ke shard atau region tertentu
  • analisis insiden dan audit

Masalah muncul saat developer menyamakan identifier internal dengan secret bearer token. Sebuah primary key tidak otomatis memenuhi sifat token autentikasi. Token autentikasi harus sulit ditebak, tidak mudah dienumerasi, dan aman walau terlihat oleh klien.

Kapan structured primary keys relevan untuk auth dan token?

1. Session store internal

Jika aplikasi menyimpan sesi di database atau key-value store, Anda mungkin ingin punya ID internal yang memuat informasi untuk keperluan operasional, misalnya waktu pembuatan atau shard. Itu sah-sah saja selama ID tersebut bukan satu-satunya nilai yang dipakai user untuk autentikasi.

Contoh yang aman:

  • session_id_internal: ID terstruktur untuk penyimpanan, partisi, dan audit
  • session_token_public: token acak yang dikirim ke browser atau klien

2. Refresh token

Refresh token umumnya hidup lebih lama daripada access token, sehingga dampak kebocorannya lebih besar. Karena itu refresh token jangan memakai ID yang bisa diprediksi atau memuat metadata sensitif. Structured key masih bisa dipakai sebagai primary key internal record refresh token, tetapi nilai yang diberikan ke klien harus opaque.

3. Upload token atau presigned workflow token

Untuk upload token, sering ada kebutuhan menautkan token dengan user, objek, waktu, atau region. Informasi itu lebih aman diletakkan di kolom database atau di payload yang ditandatangani dengan benar, bukan di identifier yang ditebak siapa pun. Jika token muncul di URL, risiko log leakage dan referrer leakage juga perlu dipertimbangkan.

4. Abuse-tracking dan fraud analysis

Structured key sering berguna di sini karena tim backend ingin cepat mengetahui asal region, bucket waktu, atau tenant yang terkait dengan event. Ini kebutuhan valid, tetapi tetap sebaiknya dibatasi untuk pipeline internal, event ID, atau row key analitik. Jangan mengorbankan keamanan token publik demi kemudahan observabilitas.

Risiko keamanan utama

Prediktabilitas dan enumerasi

Jika token publik mengikuti pola tertentu, attacker bisa mencoba menebak nilai valid berikutnya atau sebelumnya. Bahkan saat peluang menebak satu token kecil, pola yang jelas mempercepat enumerasi dan membantu mengurangi ruang pencarian.

Contoh risiko:

  • session ID berbasis timestamp + sequence
  • refresh token dengan prefix tenant dan urutan numerik
  • upload token yang berisi ID objek yang naik berurutan

Dalam sistem yang mengembalikan respons berbeda antara token tidak valid dan token kedaluwarsa, attacker juga bisa mendapatkan oracle untuk menguji tebakan.

Kebocoran metadata tenant, waktu, atau region

ID terstruktur dapat membocorkan:

  • tenant: mengungkap pelanggan tertentu aktif di sistem
  • waktu: memberi petunjuk kapan akun dibuat atau kapan sesi diterbitkan
  • region: membuka detail topologi deployment
  • volume: sequence dapat memberi gambaran throughput atau jumlah transaksi

Kebocoran seperti ini tidak selalu menyebabkan compromise langsung, tetapi sering berguna untuk reconnaissance, social engineering, atau pemetaan target bernilai tinggi.

Masalah pada audit log dan observabilitas

Structured ID memang memudahkan membaca log, tetapi ada sisi negatifnya:

  • token yang terlihat “informatif” cenderung disalin mentah ke log aplikasi, APM, atau error tracker
  • metadata sensitif ikut tersebar ke banyak sistem
  • tim bisa keliru menganggap token aman untuk dibagikan karena tampak seperti ID biasa

Jika token publik opaque dan record internal memiliki ID terpisah, log dapat tetap informatif tanpa memperbesar blast radius kebocoran.

Dampak ke index dan storage

Dari sisi database, structured primary keys kadang lebih baik daripada nilai acak penuh karena memberi lokalitas insert dan memudahkan partisi. Sebaliknya, token acak panjang kurang ideal jika dijadikan primary key cluster utama, apalagi bila sering dipakai join.

Ini salah satu alasan kuat untuk memisahkan dua kebutuhan:

  • ID internal dioptimalkan untuk database
  • token publik dioptimalkan untuk keamanan

Perbedaan kebutuhan: ID internal database vs token publik

Kesalahan desain yang umum adalah mengasumsikan keduanya punya kebutuhan yang sama. Padahal sifat yang diinginkan berbeda:

  • ID internal database: stabil, efisien untuk index, bisa membawa struktur untuk partisi/routing, mudah diaudit
  • Token publik: acak, opaque, tidak bermakna bagi klien, aman jika terlihat user, cocok sebagai bearer secret

Dengan kata lain, identifier dan credential adalah dua hal berbeda. Primary key adalah identitas record. Token adalah bukti otorisasi. Menyatukan keduanya sering membuat salah satu tujuan dikorbankan.

Skema aman: surrogate key internal + public opaque token

Pola yang paling aman dan praktis biasanya seperti ini:

  1. Gunakan surrogate key internal untuk tabel sesi atau token. ID ini boleh terstruktur jika memang membantu operasi internal.
  2. Generate public token yang opaque dan acak kriptografis.
  3. Simpan hash token di database, bukan token mentah.
  4. Tambahkan expiry, status revocation, dan metadata minimum yang diperlukan.
  5. Terapkan rate limit dan validasi respons yang konsisten.
  6. Rotasi secret atau kunci penandatanganan sesuai kebutuhan desain.

Contoh skema tabel

sessions
--------
internal_id        BIGINT / UUID / structured key internal
user_id            BIGINT NOT NULL
token_hash         BINARY / TEXT NOT NULL UNIQUE
created_at         TIMESTAMP NOT NULL
expires_at         TIMESTAMP NOT NULL
revoked_at         TIMESTAMP NULL
issued_region      TEXT NULL
tenant_id          BIGINT NULL
last_seen_at       TIMESTAMP NULL

refresh_tokens
--------------
internal_id        BIGINT / UUID / structured key internal
session_id         FK NOT NULL
token_hash         BINARY / TEXT NOT NULL UNIQUE
created_at         TIMESTAMP NOT NULL
expires_at         TIMESTAMP NOT NULL
rotated_from_id    FK NULL
revoked_at         TIMESTAMP NULL

Perhatikan bahwa yang dicari saat request masuk adalah token_hash, bukan internal_id. Dengan begitu, walaupun database bocor, token mentah tidak langsung bisa dipakai.

Contoh alur penerbitan token

// pseudo-code
publicToken = secureRandomBytes(32).base64url()
tokenHash = HMAC_SHA256(serverPepper, publicToken)

insert into sessions (
  internal_id,
  user_id,
  token_hash,
  created_at,
  expires_at,
  issued_region
) values (
  generateStructuredInternalId(),
  userId,
  tokenHash,
  now(),
  now() + sessionTtl,
  region
)

return publicToken

Mengapa di-hash dengan HMAC atau hash yang diberi pepper? Karena jika token benar-benar acak dan panjang, hash biasa pun sering cukup untuk menghindari penyimpanan plaintext. Tetapi menambahkan secret server-side memberi lapisan ekstra bila dump database terekspos.

Contoh validasi token

// pseudo-code
if token is missing or malformed:
  return unauthorized

candidateHash = HMAC_SHA256(serverPepper, token)
record = find session by token_hash = candidateHash

if no record:
  return unauthorized
if record.revoked_at is not null:
  return unauthorized
if record.expires_at < now():
  return unauthorized

// optional: bind ke device / IP heuristik, jangan terlalu agresif
return authenticated(record.user_id)

Validasi sebaiknya tidak membocorkan detail apakah token tidak ada, sudah dicabut, atau kedaluwarsa, kecuali ada alasan produk yang kuat. Respons yang terlalu detail bisa membantu attacker.

Structured primary keys yang masih masuk akal

Bukan berarti structured primary keys harus dihindari sepenuhnya. Mereka tetap berguna bila ditempatkan di layer yang tepat.

Aman untuk internal jika:

  • tidak dipakai langsung sebagai bearer token publik
  • tidak menjadi satu-satunya faktor autentikasi
  • metadata yang dikandung memang perlu untuk operasi internal
  • akses terhadap nilai tersebut dibatasi ke service dan log internal yang relevan

Contoh use case yang masuk akal:

  • ID sesi internal yang memuat bucket waktu untuk retensi dan cleanup batch
  • ID abuse event yang memuat region atau shard untuk analitik internal
  • ID upload job internal yang memudahkan routing ke worker tertentu

Dalam semua contoh ini, klien publik tetap hanya melihat token opaque atau identifier yang tidak sensitif.

Rate limit, expiry, dan rotasi: perlindungan yang sering dilupakan

Rate limit

Token yang acak pun tetap perlu rate limit. Tanpa pembatasan, attacker bisa melakukan brute-force online, terutama jika endpoint validasi murah dan dapat dipanggil masif.

Terapkan rate limit berdasarkan kombinasi:

  • IP atau subnet
  • tenant
  • account identifier bila ada
  • fingerprint perangkat secara hati-hati

Untuk endpoint sensitif seperti refresh token exchange, rate limit dan anomaly detection sering lebih penting daripada format ID itu sendiri.

Expiry

Semakin lama masa hidup token, semakin besar nilai token bagi attacker. Gunakan TTL yang sesuai:

  • session browser: disesuaikan dengan model login dan risiko
  • refresh token: lebih panjang, tetapi harus dapat dicabut dan dirotasi
  • upload token: biasanya pendek dan scope sempit

Hindari token tanpa expiry kecuali benar-benar ada kebutuhan khusus dan ada kontrol kompensasi yang kuat.

Rotasi secret dan token rotation

Ada dua jenis rotasi yang sering tertukar:

  • rotasi secret server: untuk HMAC, signing key, atau pepper
  • rotasi token: menerbitkan refresh token baru dan mencabut yang lama saat dipakai

Untuk refresh token, pola rotation membantu mendeteksi replay. Jika token lama dipakai lagi setelah rotasi, Anda bisa menandainya sebagai indikator compromise dan memutus sesi terkait.

Contoh desain untuk beberapa kasus

Session web

  • cookie menyimpan token opaque acak
  • database menyimpan hash token
  • internal session ID boleh terstruktur untuk cleanup dan observabilitas
  • set cookie dengan atribut aman yang sesuai seperti HttpOnly, Secure, dan kebijakan SameSite yang cocok

Refresh token mobile/API

  • refresh token bersifat opaque dan acak
  • record internal memiliki ID terstruktur bila perlu untuk audit atau shard
  • lakukan rotation pada setiap pertukaran token
  • catat metadata penggunaan untuk deteksi replay dan abuse

Upload token

  • scope token dibatasi ke objek/aksi tertentu
  • expiry pendek
  • jangan encode tenant atau timestamp sensitif di token yang mudah dibaca
  • jika perlu payload yang bermakna, gunakan mekanisme tanda tangan yang benar dan tetap pertimbangkan kebocoran metadata

Abuse-tracking

  • gunakan event ID internal terstruktur jika membantu pipeline analitik
  • jangan pakai ID event analitik sebagai token kontrol akses
  • pisahkan data observabilitas dari credential user-facing

Anti-pattern yang harus dihindari

  • Menggunakan auto-increment ID sebagai session token. Ini kasus klasik enumerasi.
  • Menggunakan ULID/KSUID atau ID berkomponen waktu sebagai bearer token hanya karena tampak unik. Unik tidak sama dengan rahasia.
  • Menyatukan primary key database dengan token publik. Kebutuhan performa dan keamanan bertabrakan.
  • Menyimpan token mentah di database padahal bisa menyimpan hash.
  • Mengembalikan error yang terlalu spesifik saat token invalid, expired, atau revoked.
  • Menaruh metadata sensitif di token URL tanpa mempertimbangkan kebocoran via log, history, analytics, atau referrer.
  • Tidak menerapkan rate limit karena merasa token acak sudah cukup.
  • Tidak merencanakan rotasi secret sehingga incident response menjadi sulit.

Checklist keputusan

Gunakan checklist ini saat merancang ID terstruktur untuk session dan token:

  1. Apakah nilai ini akan terlihat oleh klien atau pihak ketiga?
    Jika ya, anggap sebagai data publik atau setidaknya mudah bocor.
  2. Apakah nilai ini berfungsi sebagai bearer secret?
    Jika ya, ia harus opaque dan acak, bukan sekadar unik.
  3. Apakah ada metadata sensitif yang bocor dari format ID?
    Tenant, waktu, region, volume, atau urutan transaksi bisa bernilai bagi attacker.
  4. Apakah kebutuhan struktur ini sebenarnya hanya untuk internal DB/ops?
    Jika ya, letakkan pada internal ID atau kolom metadata, bukan token publik.
  5. Apakah token disimpan dalam bentuk hash?
    Jangan simpan plaintext jika tidak perlu.
  6. Apakah ada expiry, revocation, dan rate limit?
    Tanpa tiga hal ini, dampak kebocoran dan brute-force membesar.
  7. Apakah ada rencana rotasi?
    Baik untuk token maupun secret server-side.
  8. Apakah log dan monitoring aman?
    Pastikan token tidak tercatat mentah di banyak tempat.

Kesimpulan

ID terstruktur untuk session dan token aman hanya jika ditempatkan pada fungsi yang tepat. Untuk internal database, structured primary keys bisa sangat berguna: membantu index, audit, partisi, dan routing. Tetapi untuk nilai yang dipakai user sebagai session token, refresh token, atau upload token, struktur sering menjadi liabilitas karena membuka peluang prediktabilitas, enumerasi, dan kebocoran metadata.

Desain yang paling sering tepat adalah surrogate key internal + public opaque token. Dengan pola ini, Anda bisa tetap mendapatkan manfaat operasional dari ID terstruktur tanpa menjadikan struktur tersebut sebagai permukaan serangan. Tambahkan hashing token, validasi konsisten, expiry, rate limit, dan rotasi secret agar sistem auth dan token Anda lebih kuat secara praktis, bukan hanya rapi di atas kertas.