Hardening aplikasi Capacitor: SSL pinning via CapacitorHttp/native, `@capacitor-secure-storage` untuk Keychain & Keystore, WebView hardening config, obfuscation, enkripsi at-rest untuk data sensitif, serta audit security checklist pada aplikasi contoh.

Setelah di episode 21 kita menguasai OTA live updates, pada episode ini kita masuk ke topik kritis: keamanan aplikasi. Aplikasi mobile berjalan di perangkat yang tidak kalian kendalikan — artinya serangan bisa datang dari mana: decompiled APK, WebView inspection, atau network interception.
Mengapa keamanan penting? Karena kebocoran data user (token, lokasi, foto) bisa merusak reputasi dan melanggar regulasi (GDPR, UU PDP). Security bukan fitur — ia adalah fondasi.
SSL pinning memastikan aplikasi hanya mempercayai sertifikat tertentu (bukan semua CA yang terinstall di perangkat). Ini mencegah man-in-the-middle attack bahkan dengan sertifikat CA yang compromised.
const config: CapacitorConfig = {
plugins: {
CapacitorHttp: {
enabled: true,
// SSL pinning dikonfigurasi di level native
},
},
};Implementasi SSL pinning dilakukan di level native karena CapacitorHttp berjalan di native HTTP stack:
iOS (URLSession delegate):
import Foundation
import Capacitor
class SSLPinningDelegate: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let serverCertData = SecCertificateCopyData(certificate) as Data
let localCertData = NSData(contentsOfFile: Bundle.main.path(forResource: "cert", ofType: "der")!)! as Data
if serverCertData == localCertData {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}Android (OkHttp CertificatePinner):
import okhttp3.CertificatePinner
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()npm install @capacitor-secure-storage
npx cap syncimport { SecureStoragePlugin } from '@capacitor-secure-storage';
// Simpan token (tersimpan di Keychain/iOS atau Keystore/Android)
await SecureStoragePlugin.set({
key: 'auth_token',
value: 'eyJhbGciOiJIUzI1NiJ9...',
});
// Baca token
const token = await SecureStoragePlugin.get({ key: 'auth_token' });
console.log('Token:', token.value);
// Hapus
await SecureStoragePlugin.remove({ key: 'auth_token' });
// Cek ketersediaan
const available = await SecureStoragePlugin.isAvailable();
console.log('Secure storage available:', available.value);| Platform | Storage | Enkripsi |
|---|---|---|
| iOS | Keychain Services | AES-256-GCM |
| Android | Android Keystore | Hardware-backed (jika tersedia) |
Warning
Jangan simpan sensitive data di Preferences atau localStorage. Gunakan selalu secure storage untuk token, password, dan API keys.
const config: CapacitorConfig = {
// ...
server: {
androidScheme: 'https',
cleartext: false, // disable HTTP
},
android: {
// Android-specific hardening
allowMixedContent: false,
},
ios: {
// iOS-specific hardening
preferredContentMode: 'mobile',
},
};<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;">// Di AppDelegate.swift (iOS)
#if DEBUG
WebInspectorDebugging.enable()
#else
WebInspectorDebugging.disable()
#endifnpm install -D terser
npx terser dist/assets/*.js -o dist/assets/*.js --compress --mangle-keep class com.example.app.** { *; }
-keepclassmembers class * {
@com.getcapacitor.** *;
}import { SecureStoragePlugin } from '@capacitor-secure-storage';
import { Filesystem, Directory } from '@capacitor/filesystem';
// Untuk data besar, encrypt sebelum simpan
async function saveEncryptedData(key: string, data: string) {
// Simple XOR encryption (untuk production gunakan AES)
const encrypted = btoa(data); // base64 encoding sebagai contoh
await SecureStoragePlugin.set({ key, value: encrypted });
}
async function readEncryptedData(key: string) {
const result = await SecureStoragePlugin.get({ key });
return atob(result.value);
}Tip
Jalankan OWASP MASVS (Mobile Application Security Verification Standard) sebagai checklist keamanan. Ini adalah standar industri untuk mobile app security.
Pada episode 22 ini, kalian telah memahami:
Di episode 23 selanjutnya, kita akan membahas monitoring & crash reporting — integrasi Sentry/Firebase Crashlytics, SDK native + sourcemap JS, analytics funnel, alerting, dan release health tracking per versi app. Sampai jumpa!