@aparajita/capacitor-secure-storage

Encrypted key-value storage for Capacitor apps, backed by the iOS Keychain and Android Keystore.

Library
npm
v8.0.0
166stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
36/100Needs Attention
Development Activity4
Maintenance0
Community60
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture78
Code Quality74
Innovation66
Learning Curve72

@aparajita/capacitor-secure-storage is a Capacitor plugin that gives iOS and Android apps a simple get/set/remove API for storing sensitive data, while the underlying platform handles the actual encryption. On iOS it writes to the system Keychain (with optional iCloud Keychain sync), and on Android it encrypts values with AES-256-GCM using a key generated and held by the Android Keystore before persisting them in SharedPreferences. On the web it falls back to plain localStorage for local development and debugging only.

Beyond basic storage, it supports storing any JSON-serializable value (strings, numbers, booleans, objects, arrays) plus native Date round-tripping, a configurable key prefix to avoid collisions between apps or features, per-call or global iCloud sync control, and per-item Keychain accessibility policies (e.g. whenUnlocked, afterFirstUnlockThisDeviceOnly). It also exposes getItem/setItem/removeItem methods that conform to the @vueuse StorageLikeAsync interface, so it can be dropped in as a persistence adapter for Vue composables. It was built as a companion to @aparajita/capacitor-biometric-auth for securely storing login credentials behind biometric gates, but works standalone for any sensitive key-value data.

What You Get

  • A unified get/set/remove/keys/clear API that works identically across iOS, Android, and web
  • iOS storage in the system Keychain with optional iCloud Keychain sync (global or per-call override)
  • Android storage encrypted with AES/GCM using a key held in the Android Keystore, persisted in app-scoped SharedPreferences
  • Automatic JSON serialization for strings, numbers, booleans, objects, arrays, and Date values (with ISO 8601 round-tripping)
  • A configurable key prefix to namespace stored items and avoid collisions
  • Per-item iOS Keychain accessibility control (whenUnlocked, afterFirstUnlock, and device-only variants)
  • Low-level getItem/setItem/removeItem methods compatible with the @vueuse StorageLikeAsync interface
  • Typed StorageError with a .code you can match against StorageErrorType (missingKey, invalidData, osError, unknownError)

Common Use Cases

  • Storing auth tokens or refresh tokens for a mobile app without touching plaintext localStorage
  • Persisting login credentials behind biometric authentication alongside @aparajita/capacitor-biometric-auth
  • Caching API keys or per-user secrets that must survive app restarts but not app deletion
  • Syncing lightweight secure preferences (e.g. a saved PIN or session flag) across a user’s iOS devices via iCloud Keychain

Under The Hood

Architecture The plugin follows an abstract-base + platform-adapter pattern: SecureStorageBase (src/base.ts) implements the shared logic — key prefixing, error normalization via tryOperation(), Date-to-ISO conversion, and JSON serialization — while leaving native-facing methods (internalGetItem, internalSetItem, internalRemoveItem, getPrefixedKeys, clearItemsWithPrefix, setSynchronizeKeychain) abstract. SecureStorageWeb (src/web.ts) and SecureStorageNative (src/native.ts) each extend the base and fill in those methods: the web adapter reads/writes localStorage directly, while the native adapter proxies calls through Capacitor’s registerPlugin bridge (src/index.ts) to platform-native code — SecureStorage.java on Android (android/src/main/java/…/SecureStorage.java) encrypts values with an AndroidKeyStore-held AES/GCM key before storing them in a dedicated SharedPreferences file, while Plugin.swift on iOS (ios/Sources/SecureStoragePlugin/Plugin.swift) reads/writes the system Keychain via the Security framework. This three-tier split keeps the encryption itself entirely inside OS-provided secure storage rather than in JS.

Tech Stack Written in TypeScript (ES modules) targeting Capacitor 8, built with Rollup and vue-tsc for declaration output, and linted with oxlint, ESLint (flat config, neostandard, eslint-plugin-unicorn/vue) and Prettier. The Android side is plain Java using platform android.security.keystore APIs (KeyGenParameterSpec, KeyStore, AES/GCM/NoPadding Cipher) with no third-party crypto dependency; the iOS side is Swift using Apple’s Security framework directly, distributed via both CocoaPods and Swift Package Manager. Runtime dependencies are limited to the @capacitor/* peer family — encryption is delegated entirely to the native platforms rather than a JS crypto library.

Code Quality The repository has native-side test coverage (ios/PluginTests/PluginTests.swift) but no visible JavaScript/TypeScript unit tests for base.ts, web.ts, or native.ts. Error handling is explicit and typed: native rejections are caught in tryOperation() and re-thrown as a typed StorageError with a StorageErrorType code (missingKey, invalidData, osError, unknownError) instead of leaking raw native exceptions, and a dedicated ISO-8601 regex parser in base.ts guards Date conversion instead of relying on Date.parse. Naming is consistent (internalX/X method pairs) and the shared implementation stays in one readable ~300-line file. Enforcement is strict for a project this size — oxlint runs with —deny-warnings and vue-tsc —noEmit gates every build via the prebuild script.

API Design The public surface (SecureStoragePlugin in src/definitions.ts) is small and consistent: get/set/remove share the same (key, …optional overrides) shape, every method returns a Promise, and optional per-call parameters (sync, access, convertDate) transparently override instance-level defaults set via setSynchronize/setDefaultKeychainAccess. Errors surface as a single typed StorageError class rather than ad hoc strings. Two API tiers sit side by side: the primary get/set/remove/keys/clear methods with Date/JSON handling, and lower-level getItem/setItem/removeItem string-only methods that intentionally mirror @vueuse’s StorageLikeAsync interface, letting Vue/Ionic developers plug the plugin directly into vueuse composables. Getting started requires only the pre-wired SecureStorage export and a standard npx cap sync — no manual native linking.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search