In 2023 I argued native mobile apps were missing HttpOnly cookie support and that web apps had a security advantage as a result. Three years on, the framing was wrong on both ends. Native HTTP clients handle cookies fine. HttpOnly is a browser-context defense against document.cookie reads from injected JavaScript — a threat model that doesn't apply in a native process. The real question for mobile is on-device token storage plus binding tokens to the device.
What HttpOnly actually protects
HttpOnly is one bit on a cookie that tells the browser: don't expose this to document.cookie. The threat it counters is XSS — injected JavaScript stealing the session. Native apps don't have a DOM or untrusted JS in their main code path, so the bit has no analogue there.
The exception is embedded WebViews (WKWebView, Android WebView). If you load remote HTML/JS into one with shared cookie storage, HttpOnly does still matter inside that subsystem.
Why mobile moved to bearer tokens
Cookies still work on mobile — HTTPCookieStorage (iOS), OkHttp's CookieJar (Android), @react-native-cookies/cookies — but mobile auth standardized on bearer tokens for unrelated reasons:
- Fits OAuth 2.0 / OIDC flows
- Decouples auth state from a specific HTTP client's cookie jar
- Composes with REST and gRPC across multiple backends
- Refresh-token rotation is easier than re-running a cookie auth flow
The trade-off is that bearer tokens land in app memory and need durable storage somewhere on-device.
Storing tokens on device
Don't put tokens in UserDefaults / SharedPreferences. Don't roll your own crypto.
iOS: Keychain, with kSecAttrAccessibleWhenUnlockedThisDeviceOnly so the value never leaves the device via iCloud backup. Add SecAccessControl with .biometryCurrentSet if you want to gate access on Face/Touch ID. Key operations can run inside the Secure Enclave for hardware isolation.
Android: EncryptedSharedPreferences from androidx.security:security-crypto, backed by Keystore. On devices with hardware support, request StrongBox-backed keys via KeyGenParameterSpec.Builder.setIsStrongBoxBacked(true).
Pair with short-lived access tokens (5–15 min) and a separately-stored refresh token. Rotate refresh tokens on every use so a stolen one is single-use.
Binding tokens to the device
Bearer tokens have one inherent weakness: anyone holding the token can use it. Two specs that matured since 2023 close that gap:
- DPoP (RFC 9449, 2023) — the client signs each request with a key bound to the token. Stolen tokens without the matching key are useless.
- App Attest (iOS 14+) and Play Integrity API (Android) — your server can demand a signed attestation that the request comes from a known-good install of your app on a non-tampered device before issuing or refreshing tokens.
For the auth flow itself, passkeys / WebAuthn have replaced the password-and-session bootstrap on most platforms that needed it. They sidestep the question of where the long-lived secret lives by anchoring it in the platform's hardware-backed key store and never sending it over the wire.
The 2023 version of this question was the wrong one. The 2026 answer isn't "give native apps HttpOnly" — it's hardware-backed storage, attested clients, and proof-of-possession tokens.