Lompat ke konten utama
Mobile Keamanan

Penyimpanan Token Aman: Best Practices untuk Mobile Developer

Best practice penyimpanan token yang aman untuk developer mobile: Keychain, Keystore-backed DataStore, refresh token rotation, dan migrasi dari EncryptedSharedPreferences.

Secure Token Storage: Best Practice untuk Developer Mobile

Simpan token autentikasi di storage terenkripsi bawaan platform, bukan di WebView localStorage atau SharedPreferences biasa. Di iOS, gunakan Keychain. Di Android, lindungi key dengan Android Keystore (software, TEE, atau StrongBox tergantung perangkat) dan simpan ciphertext menggunakan Jetpack DataStore + Tink. EncryptedSharedPreferences sudah deprecated di androidx.security.crypto; rencanakan migrasi sekarang. Untuk Capacitor aplikasi, gabungkan transport security (HTTPS, pinning) dengan plugin storage berbasis native daripada menduplikasi crypto di JavaScript.

Cara Kami Memilih Pola Storage Ini

Setiap rekomendasi di sini melewati empat filter. Pertama, hardware backing: material rahasia harus tersimpan di Keychain/Keystore atau dienkripsi dengan kunci yang ada di sana. Kedua, jalur migrasi resmi — jika Google atau Apple sudah men-deprecated metodenya, kami tidak merekomendasikannya untuk code baru. Ketiga, kompatibilitas Capacitor: pola ini harus bekerja dari plugin boundary tanpa mengekspos raw keys ke JavaScript. Keempat, revocability: kredensial yang bocor harus bisa dibatalkan di sisi server.

Filter terakhir itulah alasan mengapa refresh token rotation muncul di seluruh artikel ini. Dalam tinjauan data insiden 2024 yang disusun Chainalysis, kompromi yang melibatkan private keys dan kredensial memicu mayoritas pencurian cryptocurrency — termasuk lebih dari $305 juta dari DMM Bitcoin di Jepang dan $235 juta dari WazirX di India — dan kegagalan umumnya adalah kepercayaan penuh pada satu kredensial yang valid terus-menerus ("laporan kriminal Chainalysis 2024"). Auth token mobile gagal dengan cara yang sama: bearer token berumur panjang dalam plaintext storage adalah celah keamanan. Kami tidak akan merekomendasikan arsitektur apa pun yang menyimpan kredensial non-rotating secara permanen.

Prinsip yang berlaku di setiap platform

Tabel di bawah ini diringkas menjadi snapshot; logika dasarnya adalah pemisahan access/refresh token. Sebuah access token berumur pendek — hitungan menit, bukan hari — dan merupakan apa yang disebut API sebagai 'present'. Karena blast radius-nya kecil dan cepat kedaluwarsa, token ini cukup disimpan di memori saja, dan dibuat ulang setiap kali aplikasi dijalankan. Sedangkan refresh token adalah rahasia permanen yang memungkinkan Anda membuat access token baru, sehingga hanya token ini yang layak disimpan di storage terenkripsi berbasis hardware. Masa berlaku yang berbeda memerlukan tempat penyimpanan yang berbeda; menyimpan keduanya dengan cara yang sama adalah penyebab utama kebocoran.

Urutan lengkap saat runtime adalah sebagai berikut: aplikasi berjalan tanpa access token, memanggil server auth Anda dengan refresh token yang tersimpan, menerima access token baru di memori, dan menghapus refresh token lama karena server melakukan rotasi pada setiap penggunaan. Jika server mendeteksi refresh token yang sudah terpakai muncul kembali, server akan menganggapnya sebagai bukti pencurian dan mencabut seluruh rangkaian token tersebut. Saat logout, client menghapus local storage dan server mencabut grant — menghapus data secara lokal tanpa pencabutan di server hanyalah sebuah harapan, bukan keamanan.

Praktik Terbaik Mengapa ini penting
Access token berumur pendek Membatasi dampak jika token bocor
Rotasi refresh token Membatalkan refresh token yang dicuri setelah penggunaan sah
Hanya HTTPS Mencegah intersepsi jaringan biasa
Certificate pinning (jika diperlukan) Mengurangi risiko root kustom atau tidak valid; lihat panduan certificate pinning OWASP
Jangan simpan token di logs, URL, atau analytics Token di tools observability bisa memicu kebocoran data
Revokasi saat logout Invalidasi sisi server lebih aman daripada mengandalkan client untuk hapus data

@capgo/capacitor-ssl-pinning bisa melakukan pinning TLS untuk Capacitor HTTP di iOS dan Android saat butuh kontrol transport yang lebih ketat.

Android: Keystore dan DataStore (bukan EncryptedSharedPreferences)

Google telah mendepresiasi androidx.security.crypto helper, termasuk EncryptedSharedPreferences dan MasterKey. Ringkasan package menyarankan developer untuk menggunakan KeyGenerator dengan provider AndroidKeyStore untuk key material baru.

Gunakan satu pipeline berbasis Tink secara end-to-end:

  1. Buat keyset Tink dengan AndroidKeysetManager, menyimpan master key di Android Keystore melalui android-keystore:// URI (lihat datastore-tink catatan rilis).
  2. Dapatkan Aead primitif dari keyset tersebut.
  3. Tambah androidx.datastore:datastore-tink (tersedia dari DataStore 1.3.0-rc01; versi stabil 1.2.x tidak menyertakan artefak ini), bungkus Serializer dengan AeadSerializer, dan teruskan ke DataStore. Saat beberapa store menggunakan satu Aead key, berikan setiap store associatedDatayang unik, seperti nilai stabil dari nama filenya.

Google mendokumentasikan ini sebagai jalur pengganti resmi untuk yang sudah deprecated androidx.security.crypto helpers. Jangan mencampur JCA standalone SecretKey dengan AeadSerializer; serializer mengharapkan instance Tink Aead .

Catatan tentang versioning: karena datastore-tink artifact hanya tersedia mulai versi 1.3.0-rc01 ke atas, tim yang menggunakan versi stable 1.2.x tidak akan menemukan pengganti yang didokumentasikan di dependency tree mereka. Celah ini sudah berlangsung beberapa rilis, dan menurut pandangan kami, inilah alasan utama rencana migrasi sering terhambat — putuskan dengan sengaja apakah ingin menggunakan release candidate atau menunggu, jangan sampai terjadi secara tidak sengaja.

Opsi Keystore yang perlu diketahui

  • setIsStrongBoxBacked(true) saat FEATURE_STRONGBOX_KEYSTORE tersedia untuk isolasi key yang lebih kuat.
  • Autentikasi pengguna binding (setUserAuthenticationRequired) saat token hanya boleh diakses setelah biometrik atau kredensial perangkat.
  • Hindari Auto Backup untuk file yang menyimpan keyset atau ciphertext kecuali Anda paham perilaku restore; dokumentasi EncryptedSharedPreferences Google memperingatkan tentang restore backup ciphertext tanpa keys.

Modul Native Kotlin atau Java di Capacitor harus mengimplementasikan pola ini di dalam plugin; JavaScript hanya boleh menerima opaque handles atau session state, bukan raw keys.

iOS: Keychain dan Secure Enclave

Skill Keychain tetap menjadi tempat yang tepat untuk menyimpan token di iOS. Item dienkripsi dan dilindungi oleh sistem. Gunakan konstanta accessibility dengan scope sempit dan Kontrol Akses saat Anda membutuhkan Face ID, Touch ID, atau gating passcode.

Pilihan accessibility adalah keputusan krusial, bukan sekadar default untuk disalin. Pilih kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly saat token harus bisa digunakan oleh background work — push-triggered refreshes, background fetch — tetapi tidak boleh bermigrasi ke perangkat baru via backup. Pilih kSecAttrAccessibleWhenUnlockedThisDeviceOnly untuk penggunaan token foreground-only, di mana batasan yang lebih ketat sebanding dengan keamanannya. Dalam kedua kasus, aktifkan kSecUseDataProtectionKeychain pada target modern agar Anda mendapatkan data-protection Keychain, bukan versi legacy berbasis file.

CryptoKit membantu saat Anda mengenkripsi payload sendiri, tetapi untuk bearer token standar gaya OAuth, menyimpan string token langsung di Keychain dengan aksesibilitas yang tepat biasanya sudah cukup. Kunci berbasis Secure Enclave (kunci P-256 yang dibuat dengan SecureEnclave di CryptoKit) hanya berguna jika kunci tersebut tidak boleh keluar dari hardware aman — seperti operasi signing atau device attestation. Kunci tersebut tidak bisa menyimpan string token; mereka melindungi envelope key Anda. Jika Anda tidak melakukan rotasi envelope dan mengelola lifecycle-nya, penyimpanan Keychain biasa lebih simpel dan tetap aman. Enkripsi tambahan di layer aplikasi hanya membantu jika Anda juga melakukan rotasi kunci dan mengelola envelope dengan teliti.

Capacitor’s @capacitor/preferences menulis ke UserDefaults, yang merupakan tidak penyimpanan rahasia. Jangan simpan token di sana.

Capacitor: opsi penyimpanan aman di organisasi Cap-go

Cap-go melakukan tidak merilis paket bernama “secure storage,” namun ada dua plugin yang memenuhi kebutuhan Capacitor umum:

Plugin Terbaik untuk Dukungan Native
@capgo/capacitor-native-biometric setData / getSecureData Secret kecil (session tokens, API keys) iOS Keychain; enkripsi berbasis Android Keystore di SharedPreferences
@capgo/capacitor-data-storage-sqlite dengan opsi enkripsi Data lokal yang lebih besar atau terstruktur SQLite dengan opsi SQLCipher

Skill panduan penyimpanan key-value membandingkan Preferences, penyimpanan SQLite, dan NativeBiometric.setData untuk data rahasia.

Membutuhkan @capgo/capacitor-native-biometric 8.6.0 atau lebih baru (sesuaikan versi major plugin dengan versi major Capacitor Anda). Contoh pembacaan dengan proteksi biometrik:

import {
  AccessControl,
  NativeBiometric,
} from '@capgo/capacitor-native-biometric';

const TOKEN_KEY = 'auth.refresh_token';

export async function saveRefreshToken(token: string) {
  await NativeBiometric.setData({
    key: TOKEN_KEY,
    value: token,
    accessControl: AccessControl.BIOMETRY_CURRENT_SET,
  });
}

export async function loadRefreshToken(): Promise<string | null> {
  try {
    const { value } = await NativeBiometric.getSecureData({
      key: TOKEN_KEY,
      reason: 'Unlock your session',
      title: 'Authenticate',
    });
    return value ?? null;
  } catch {
    return null;
  }
}

getData hanya mengembalikan nilai yang disimpan tanpa accessControl. Untuk proteksi biometrik, selalu pasangkan BIOMETRY_CURRENT_SET atau BIOMETRY_ANY dengan getSecureData.

Untuk web build, jangan pernah simpan token produksi di localStorage. Gunakan model web session backend Anda sebagai gantinya.

Lifecycle token pada server dan client

Ya, aplikasi mobile harus menyimpan refresh token — alternatifnya adalah memaksa pengguna re-autentikasi setiap kali aplikasi dibuka, yang mana itu hanya 'security theater' dan lebih buruk dari keamanan asli. Nuansanya adalah di mana dan bagaimana: simpan hanya refresh token, hanya di storage berbasis Keychain atau Keystore, dan hanya dengan mekanisme rotation serta server-side revocation.

Rotation adalah kunci agar persistence tetap aman. Terbitkan refresh token sebagai one-time-use credentials: setiap kali digunakan, ia akan mengembalikan refresh token baru dan membatalkan yang lama. Server menyimpan jendela singkat deteksi-penggunaan-ulang jendela — jika token yang seharusnya sudah terpakai muncul lagi, server akan menganggap seluruh rangkaian token telah kompromi dan mencabut seluruh chain, memaksa login ulang di setiap perangkat yang menggunakannya. Berikan grace period singkat (beberapa detik hingga satu menit) untuk racing requests atau reconnect saat offline, di mana client yang sah mungkin mengirimkan token lama satu kali terakhir sebelum menerima token pengganti. Tanpa grace period sama sekali akan mengunci user pada koneksi yang tidak stabil; namun jika window terlalu lama, akan memperluas attack surface.

Lokasi penyimpanan setiap token bergantung pada masa berlakunya:

  • Terbitkan access token dengan TTL singkat (menit, bukan hari), dan simpan hanya di dalam memory selama sesi berlangsung.
  • Simpan refresh token di Keychain atau encrypted storage berbasis Keystore, lalu lakukan rotasi seperti di atas.
  • Saat ganti password, logout, atau perangkat hilang, cabut (revoke) refresh token di sisi server — menghapus data lokal di client bukan berarti revocation.
  • Pertimbangkan strategi token binding ringan (device key attestation atau app instance IDs) jika model ancaman Anda membutuhkannya.

Saat Anda merilis perbaikan keamanan pada penanganan token di aplikasi Capacitor, Capgo live updates dapat mengirimkan perubahan web-layer dengan cepat selama tetap mematuhi kebijakan store. Perubahan pada native storage tetap memerlukan build store.

Perbandingan opsi storage

Metode Keamanan Capacitor fit
Variabel JS in-memory Tinggi saat berjalan, hilang saat aplikasi dimatikan OK untuk access token selama sesi
localStorage / Preferensi Rendah Hanya untuk Settings, bukan secrets
iOS Keychain / Android Keystore Tinggi Lewat native code atau plugin Capgo
DataStore + Tink (native Android) Tinggi Plugin Capacitor custom atau native auth SDK
Custom crypto di JS Variabel Hindari; key di JS bisa diekstrak

Perlu dicatat pada baris terakhir: masalah JavaScript crypto bukanlah algoritma yang lemah, melainkan penempatannya. Key apa pun yang bisa dibaca runtime JS dapat dieksfiltrasi oleh apa pun yang ada di runtime tersebut, jadi "keamanan tinggi" secara struktural tidak bisa dicapai dari layer itu, apa pun library yang Anda pilih.

Checklist migrasi dari EncryptedSharedPreferences

  1. Inventarisasi key mana yang menyimpan secret vs config publik.
  2. Implementasikan DataStore + AeadSerializer dengan keyset Tink yang didukung Keystore.
  3. Migrasikan dengan SharedPreferencesMigration menggunakan producer yang membuka EncryptedSharedPreferences instance (bukan plain SharedPreferences), lalu hapus file legacy setelah berhasil dibaca.
  4. Rilis perubahan native lewat update store; uji backup dan restore di perangkat fisik.
  5. Hapus androidx.security.crypto dependencies kalau tidak ada yang mereferensikannya.

FAQ

Apakah saya perlu mengenkripsi token sebelum menyimpannya di Keychain?

Biasanya tidak butuh lapisan tambahan untuk bearer token standar jika aksesibilitas Keychain sudah sesuai threat model Anda. Tambahkan envelope encryption kalau regulasi atau kebijakan rotasi key mewajibkannya.

Apakah SQLCipher di plugin Capacitor bisa menggantikan Keystore?

SQLCipher melindungi file database, tapi Anda tetap butuh key yang kuat di Keystore atau Keychain untuk mendekripsi database tersebut. Perlakukan penanganan passphrase enkripsi sebagai bagian dari desain secure storage Anda.

Bisakah saya menyimpan token di Capacitor Preferences dengan obfuscation?

Obfuscation bukan enkripsi. Preferences sebenarnya adalah UserDefaults dan SharedPreferences, tanpa perlindungan berbasis hardware.

Haruskah aplikasi mobile menyimpan refresh token?

Ya — tapi hanya di Keychain atau encrypted storage berbasis Keystore, dipasangkan dengan rotasi dan pencabutan di sisi server. Kalau tidak disimpan, user harus re-autentikasi setiap kali aplikasi dibuka, dan itu biasanya berujung mereka menuntut login yang lebih lemah. Yang sama sekali tidak boleh adalah menyimpan refresh token di Preferences, localStorage, atau file plaintext apa pun.

Apa cara paling aman untuk menyimpan access token di aplikasi modern?

Jangan simpan sama sekali. Simpan access token yang berumur pendek di memori selama sesi berlangsung dan buat ulang dari refresh token setiap kali aplikasi dijalankan. Simpan hanya refresh token — di iOS Keychain atau storage terenkripsi berbasis Android Keystore — dan biarkan mekanisme rotasi serta pencabutan (revocation) membatasi dampak jika perangkat berhasil dikompromi.

Lanjut membaca Secure Token Storage: Best Practices for Mobile Developers

Jika Anda menggunakan Penyimpanan Token yang Aman: Best Practices untuk Mobile Developer untuk merencanakan keamanan dan kepatuhan, hubungkan dengan Enkripsi untuk detail implementasi di bagian Encryption, Kepatuhan untuk detail implementasi di Compliance, Capgo Security Scanner untuk alur kerja produk di Capgo Security Scanner, Keamanan Capgo untuk alur kerja produk di Capgo Security, dan Capgo Trust Center untuk alur kerja produk di Capgo Trust Center.

Live update untuk aplikasi Capacitor

Saat bug di web layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna menerima update di background, sementara perubahan native tetap di jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.