Secure token storage in Capacitor apps
Where you keep auth tokens decides how secure your app really is. How to store tokens safely in Capacitor using the iOS Keychain and Android Keystore — not localStorage — and why it matters for OTA.
Where you keep auth tokens decides how secure your app actually is. It's tempting to drop a JWT in localStorage because it's one line — but on a mobile device that storage is readable in ways it isn't on a locked-down web origin. This guide covers doing it right in Capacitor with the iOS Keychain and Android Keystore, and why it matters for the whole app, including OTA.
Rule: secrets — access tokens, refresh tokens, encryption keys — go in the platform secure storage. Non-sensitive state can live in web storage. If leaking it would compromise an account, it doesn't belong in localStorage.
Why localStorage is the wrong home
- It's plain text in the WebView's storage, not hardware-backed.
- It persists indefinitely and isn't tied to device unlock or biometrics.
- On a compromised or rooted device it's trivially readable.
Use the platform keystore
The iOS Keychain and Android Keystore are purpose-built for this: OS-level, often hardware-backed, and able to require device unlock or biometrics before releasing a value. Use a Capacitor secure-storage plugin that wraps them:
// store on login
await SecureStorage.set({ key: 'refresh_token', value: token });
// read when you need it
const { value } = await SecureStorage.get({ key: 'refresh_token' });Gate access with biometrics
For extra-sensitive tokens, require a biometric check before the keystore releases the value — combining the two gives you “encrypted at rest and unlocked only by the user.” See biometric authentication.
The connection to OTA security
Token storage and update integrity are two halves of the same posture: don't trust the client blindly. OtaKit signs and verifies every bundle before it activates, so an attacker can't push code that exfiltrates your tokens; you store the tokens where that code — or any other — can't read them casually. Neither alone is enough. See OTA update security.
When a token leaks or a session must end, you want to revoke server-side and clear the keystore client-side. Build a “sign out everywhere” path that does both — and you can ship improvements to that path over the air.
Where to go next
See Security for OtaKit's model, and social login for where these tokens come from.