Rethinking Security: The Need for Cookie Support in Native Mobile Applications

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.