TL;DR: Keep secrets out of ordinary files and preferences. On Android, generate keys in the Android Keystore (hardware-backed in a TEE or StrongBox where available, non-exportable), and use them to encrypt any token you must persist. On iOS, store secrets in the Keychain with an accessibility class such as
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, and put signing or key-agreement keys in the Secure Enclave. Hardware backing protects the key material from being copied off the device; it does not stop malware or an attacker controlling the app from asking the hardware to use the key, and it does not protect plaintext once the app decrypts it. So minimise what is stored, prefer short-lived tokens, bind high-risk keys to user authentication, exclude secrets from backups, and keep the server authoritative.
How to approach it
Start from what must be stored at all (often only a refresh token or a device key), then the platform mechanism on each OS, then the precise guarantee of hardware backing, then the leaks that bypass secure storage entirely (logs, backups, screenshots, the clipboard). End with what changes on a rooted or jailbroken device.
A strong answer
SharedPreferences or plist
Token in a file
app sandbox
Readable on rooted devices and in some backups
Outcome, unsafe: Copyable to another device
Stolen token works anywhere
Keystore or Keychain + Secure Enclave
Non-exportable key
TEE, StrongBox, Secure Enclave
Never leaves hardware
Token encrypted with it
this device only
Outcome, safe: Useless off the device
Add user-auth requirements for sensitive keys
Store less. The best secret is one the app does not hold. Keep access tokens in memory with short lifetimes; persist only a refresh token or a device-bound key; never embed long-lived server secrets in the app package, because anything shipped in the binary can be extracted.
Android.
- Generate keys inside the Android Keystore with
KeyGenParameterSpec. The key material lives in the TEE or, withsetIsStrongBoxBacked(true), a separate secure element, and is not exportable. Key attestation can prove to your server that a key was generated in hardware. - Encrypt the refresh token (AES-GCM with a Keystore key) before writing it to app-private storage. The Jetpack
EncryptedSharedPreferenceshelper did this for you, but it has been deprecated, so many teams now wrap the Keystore directly or use a maintained library. - Bind sensitive keys to user authentication (
setUserAuthenticationRequired) so a key is usable only after a biometric or device credential check, and consider invalidation when new biometrics are enrolled. - Exclude secrets from cloud backup and device-to-device transfer through backup rules (
dataExtractionRules), or setallowBackupto false for sensitive apps.
iOS.
- Store tokens in the Keychain with an appropriate accessibility class.
WhenUnlockedThisDeviceOnlymakes the item available only while the device is unlocked and prevents it migrating to another device through backups.AfterFirstUnlockis needed for background refresh but leaves the item available whenever the device has been unlocked once since boot. - For keys the app uses to sign or derive (device binding, passkeys), create them in the Secure Enclave (P-256), where the private key never leaves the coprocessor. Add access control flags requiring user presence or biometry for high-risk operations.
- Data Protection classes apply to files too; sensitive files should use complete protection where background access is not needed.
What hardware backing actually guarantees. The key cannot be copied to another device, even by a root-level attacker, because only the secure hardware holds it. It does not guarantee:
- that malicious code inside your app, or an attacker who controls the process on a compromised device, cannot ask the hardware to decrypt or sign while the device is unlocked;
- that decrypted tokens in memory are safe;
- that a token you stored as plaintext in a file is protected (the key protects only what you encrypt with it).
So on a rooted or jailbroken device, assume an attacker can use the key in place while they control the app, but cannot exfiltrate it for use elsewhere. That is why device-bound keys plus server-side checks of key attestation and anomalous use are the strong pattern.
Leaks that bypass storage entirely. Tokens in logs (Log.d, NSLog, crash reports and analytics SDKs), in URLs, on the clipboard, in WebView caches and cookies, in screenshots captured for the app switcher (mask sensitive screens), in keyboard caches for fields not marked secure, and in backups of the app's files. A review checks these before it checks the keychain flags.
What interviewers probe next
- "Is base64 or obfuscation of a token in SharedPreferences acceptable?" No; it is encoding, readable by anyone with file access on a rooted device or from an unencrypted backup.
- "How would you bind a session to the device?" Generate a non-exportable key in the Keystore or Secure Enclave at enrolment, register the public key with the server, and have the app sign challenges or token requests, so a stolen refresh token alone is useless.
- "What happens to Keychain items when the app is deleted?" On iOS they may persist across reinstall, so apps that need a clean slate clear them on first launch after install.
What if?
What if · interview follow-ups
Hold your answer as the constraints change
A mobile app must keep a refresh token on the device so users stay signed in for weeks.
Start here
Where should the token not be stored?
Who can read shared preferences?
In ordinary files, shared preferences, user defaults or databases in plaintext, which backups, rooted devices and forensic tools can read.
Carry it forwardNever store secrets in plain files or preferences.
Weak answerBase64-encoding the token in preferences.
Constraint changes
How should Android store it?
Where does the encryption key live?
Generate a key in the Android Keystore, hardware-backed in a TEE or StrongBox where available and non-exportable, and use it to encrypt the token before persisting.
Carry it forwardKeystore keys encrypt persisted tokens.
Weak answerHard-coding an encryption key in the app.
Constraint changes
How should iOS store it?
Which accessibility class prevents migration?
In the Keychain with an accessibility class such as 'this device only, after first unlock', so it is not synced or included in backups to other devices.
Carry it forwardKeychain with a device-only accessibility class.
Weak answerDefault accessibility that syncs to other devices.
Constraint changes
What if the device is stolen?
Can the server cut off this device?
Hardware-backed storage makes extraction hard, and the server should be able to revoke refresh tokens per device. Require re-authentication for sensitive actions so a stolen session cannot do everything.
Carry it forwardServer-side revocation and step-up.
Weak answerNo way to revoke a device's tokens.
Constraint changes
Can the token be bound to the device?
What if someone copies the token off the phone?
Yes: bind refresh tokens to a hardware-backed key, for example with DPoP or a signed challenge, so a copied token is useless elsewhere.
Carry it forwardBind tokens to device keys.
Weak answerBearer refresh tokens with long lifetimes.
Now defend it
What storage design would you implement?
Encrypt the refresh token with a non-exportable Keystore key on Android and store it in the Keychain with a device-only accessibility class on iOS, bind it to a hardware-backed key, rotate it on use, support per-device revocation on the server and require step-up for sensitive actions.
How it's scored
Weak: "Encrypt the token and store it in SharedPreferences."
Hire: Uses the Keystore and Keychain with appropriate settings, explains hardware backing and non-exportable keys, and excludes secrets from backups.
Strong Hire: Minimises stored secrets, chooses accessibility classes deliberately, binds keys to user authentication and to the device with attestation, states precisely what hardware backing does not protect, and lists the non-storage leaks (logs, screenshots, clipboard, keyboard caches).
Common mistakes
- Hard-coding a server API key in the app and calling it "obfuscated".
- Using
AfterFirstUnlockeverywhere without needing background access. - Believing hardware backing protects a compromised app process.
- Forgetting backups and logs.