Menyimpan token autentikasi (JWT, session token, atau refresh token) di dalam AsyncStorage adalah celah keamanan kritikal yang sering ditemui pada aplikasi React Native. AsyncStorage menyimpan data dalam bentuk file teks biasa (plaintext) atau database SQLite tanpa enkripsi di direktori lokal aplikasi. Pada perangkat yang di-root atau di-jailbreak, token ini dapat diekstraksi langsung melalui file system atau via Android Debug Bridge (ADB) backup jika tidak dimitigasi secara eksplisit.

Anatomi Kerentanan: Mengapa AsyncStorage Berbahaya

Secara arsitektur, AsyncStorage dirancang untuk menyimpan preferensi UI non-sensitif, bukan kredensial kriptografis. Terdapat tiga vektor serangan utama terhadap penyimpanan token di AsyncStorage:

  • Root/Jailbreak File Access: Penyerang atau malware dengan akses root dapat langsung membaca direktori /data/data/com.yourapp/databases/RKStorage di Android atau Library/Application Support di iOS tanpa halangan.
  • ADB Backup Extraction: Secara default, atribut android:allowBackup="true" pada AndroidManifest.xml memungkinkan seluruh isi AsyncStorage diekspor ke file .ab tanpa memerlukan hak akses root.
  • Memory Dumps & Plaintext Inspection: Data dari storage dibaca langsung ke JavaScript heap tanpa proteksi memori terisolasi, memudahkan inspeksi runtime via framework instrumen seperti Frida.

Arsitektur Hardware-Backed Storage: TEE, StrongBox, dan Secure Enclave

Untuk mengamankan token secara deterministik, kunci enkripsi dan data sensitif harus dilindungi oleh modul komputasi terisolasi di tingkat silikon:

  • iOS Keychain & Secure Enclave (SEP): Kunci enkripsi dibuat di dalam prosesor terisolasi (Secure Enclave) yang memiliki memori dan boot ROM terpisah dari prosesor aplikasi utama. Atribut kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly memastikan data dienkripsi saat perangkat terkunci dan kuncinya tidak pernah disinkronkan ke backup iCloud.
  • Android Keystore, TEE, dan StrongBox: Android mengisolasi materi kriptografis di dalam Trusted Execution Environment (TEE). Pada perangkat modern (Android 9+), modul StrongBox Keymaster menyediakan hardware terdedikasi (HSM independen dengan CPU, timer, dan storage anti-tamper tersendiri) yang kebal terhadap eksploitasi kernel OS utama.

Implementasi Proteksi Token via react-native-keychain

Library react-native-keychain membungkus implementasi native iOS Keychain Services dan Android KeyStore API ke dalam antarmuka JavaScript yang konsisten. Instalasi pustaka:

npm install react-native-keychain
# atau
yarn add react-native-keychain

1. Modul Manajemen Token (TokenStorage.ts)

Berikut adalah implementasi modul penyimpanan token dengan memanfaatkan level proteksi hardware, isolasi backup, dan fallback otomatis:

import * as Keychain from 'react-native-keychain';

const AUTH_SERVICE = 'com.yourapp.auth';

interface StoreTokenOptions {
  requiresBiometrics?: boolean;
}

export const storeAuthToken = async (
  token: string,
  options: StoreTokenOptions = {}
): Promise<boolean> => {
  try {
    const accessControl = options.requiresBiometrics
      ? Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE
      : Keychain.ACCESS_CONTROL.NONE;

    const result = await Keychain.setGenericPassword('authToken', token, {
      service: AUTH_SERVICE,
      // iOS: Kunci tidak pernah diekspor ke iCloud Backup dan hanya dapat diakses saat device aktif
      accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
      accessControl,
      // Android: Paksa enkripsi menggunakan StrongBox / TEE hardware isolation
      securityLevel: Keychain.SECURITY_LEVEL.SECURE_HARDWARE,
    });

    return result !== false;
  } catch (error) {
    // Fallback strategi jika hardware level StrongBox tidak didukung perangkat
    return await storeTokenWithFallback(token);
  }
};

const storeTokenWithFallback = async (token: string): Promise<boolean> => {
  try {
    const fallbackResult = await Keychain.setGenericPassword('authToken', token, {
      service: AUTH_SERVICE,
      accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
      // Fallback ke software keystore jika TEE/StrongBox hardware error
      securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE,
    });
    return fallbackResult !== false;
  } catch (error) {
    console.error('Gagal menyimpan token ke Keystore:', error);
    return false;
  }
};

export const getAuthToken = async (): Promise<string | null> => {
  try {
    const credentials = await Keychain.getGenericPassword({
      service: AUTH_SERVICE,
    });

    if (credentials && credentials.password) {
      return credentials.password;
    }
    return null;
  } catch (error) {
    console.error('Gagal mendekripsi token dari Keystore:', error);
    return null;
  }
};

export const clearAuthToken = async (): Promise<boolean> => {
  try {
    return await Keychain.resetGenericPassword({ service: AUTH_SERVICE });
  } catch (error) {
    console.error('Gagal menghapus token dari Keystore:', error);
    return false;
  }
};

Strategi Fallback dan Keamanan Bertingkat

Meskipun arsitektur StrongBox menawarkan proteksi terbaik, implementasi di lapangan harus memperhitungkan variasi fragmentasi Android:

  1. Level 1 (Ideal): SECURITY_LEVEL.SECURE_HARDWARE (StrongBox HSM terdedikasi atau ARM TrustZone TEE). Mencegah dump memory secara fisik maupun via eksploitasi root.
  2. Level 2 (Fallback): SECURITY_LEVEL.SECURE_SOFTWARE. Tetap mengenkripsi secret menggunakan master key Keystore di level OS, meski isolasi fisik tidak tersedia pada chipset low-end atau emulator testing.
  3. Deteksi Kegagalan: Selalu tangkap pengecualian KeyStoreSafeModeException atau kegagalan inisialisasi kriptografi untuk mengalihkan ke fallback yang terkontrol, bukan membuat aplikasi crash.

Checklist Hardening Pelengkap

Penggunaan hardware Keystore harus didukung dengan hardening di level platform OS agar celah bypass sisi klien tertutup sepenuhnya.

1. Nonaktifkan OS Auto Backup

Cegah Android menyalin state lokal aplikasi ke Google Drive atau via koneksi debug USB (ADB). Pada android/app/src/main/AndroidManifest.xml:

<application
    android:name=".MainApplication"
    android:allowBackup="false"
    android:fullBackupContent="false"
    ... >

2. Blokir Screen Capture dan App Switcher Leak (FLAG_SECURE)

Auth token yang ditampilkan di layar atau state UI yang menyimpan token sementara dapat ter-expose melalui screenshot atau task snapshot saat aplikasi masuk background. Di Android, aktifkan FLAG_SECURE di dalam MainActivity.java atau MainActivity.kt:

import android.os.Bundle;
import android.view.WindowManager;
import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().setFlags(
      WindowManager.LayoutParams.FLAG_SECURE,
      WindowManager.LayoutParams.FLAG_SECURE
    );
  }
}

3. Pembersihan Memori Komprehensif saat Logout (Secure Wipe)

Menghapus token di Keystore tidak serta-merta membersihkan state yang tersisa di memory space JavaScript engine (Hermes/JSC). Lakukan sanitasi menyeluruh:

  • Panggil Keychain.resetGenericPassword() untuk menghapus representasi fisik di native storage.
  • Hapus cache in-memory API client (misal: Axios interceptor headers, instance Apollo Client, atau cache RTK-Query).
  • Reset global root state (Redux/Zustand) ke initial state untuk memastikan token tidak tertinggal di RAM heap.