Purchase entitlements survive app reinstall and device transfer
Why it matters
Users who reinstall an app or transfer to a new device lose any premium state stored only in local storage (AsyncStorage, SharedPreferences, UserDefaults, MMKV). CWE-311 (Missing Encryption of Sensitive Data) is a related vector — but the core failure here is data-integrity: the entitlement record does not survive the lifecycle events it must. The business consequence is straightforward: paying users who lose their subscription after a reinstall file chargebacks directly with their bank or credit card company, which harms the developer's payment standing. They also leave negative reviews and generate disproportionate support volume. ISO/IEC 25010 reliability requirements apply — a subscription entitlement is a durable system state that must survive device lifecycle events.
Severity rationale
High because entitlement loss after reinstall causes paying users to file chargebacks and leave negative reviews, with direct financial and reputational impact.
Remediation
Call the platform entitlement refresh on every app startup — not only after a purchase. Never rely solely on a local flag that gets wiped on reinstall.
// App.tsx or your app initialization hook
useEffect(() => {
const restoreEntitlements = async () => {
if (isLoggedIn) {
const customerInfo = await Purchases.getCustomerInfo();
setIsPremium(customerInfo.entitlements.active['premium'] !== undefined);
}
};
restoreEntitlements();
}, [isLoggedIn]);
For server-based entitlements, fetch subscription status from your backend (GET /api/user/subscription or equivalent) at login and on app-foreground events. Store the result in memory or a local cache that is treated as derived, not authoritative — the server or platform SDK is the source of truth.
Detection
- ID:
reinstall-survival - Severity:
high - What to look for: Count all relevant instances and enumerate each. Determine how entitlement state is stored and restored. The critical question: does the app rely solely on local storage for premium status? Look for entitlement state stored in:
AsyncStorageonly (React Native),SharedPreferencesonly (Android),UserDefaultsonly (iOS),MMKVonly — all of these are wiped on app reinstall or device transfer, meaning premium status is lost. The correct patterns are: (1) server-side entitlement: premium status fetched from your backend at login (keyed to user account, not device); (2) RevenueCat/Adapty/QonversioncustomerInfofetched fresh on app launch — these query Apple/Google servers and survive reinstall; (3) StoreKit 2'sTransaction.currentEntitlementssequence (async stream) — queries the App Store on-demand and survives reinstall. Search for a startup sequence that restores entitlements:Purchases.getCustomerInfo()inApp.tsxor equivalent initialization, a server call likeGET /api/user/subscriptionorGET /api/entitlementsin the auth/startup flow, orfor await (let transaction in Transaction.currentEntitlements)in Swift. Fail pattern: premium flag written toAsyncStorageat purchase time and read fromAsyncStorageat startup — this data does not survive reinstall. - Pass criteria: Entitlement state is recovered from the platform store (via restore API, SDK's
getCustomerInfo(), or StoreKit 2currentEntitlements) or from a server database on app startup or at login. At least 1 implementation must be verified. Premium access does not depend solely on local device storage. - Fail criteria: Premium status stored only in local storage (
AsyncStorage,SharedPreferences,UserDefaults, MMKV, SQLite on-device) with no mechanism to re-derive it from the store or a server after reinstall. - Skip (N/A) when: No IAP detected in the app.
- Detail on fail:
"isPremium flag stored in AsyncStorage at purchase time and read from AsyncStorage on startup — premium status is permanently lost after app reinstall"or"No call to Purchases.getCustomerInfo() or equivalent on app startup — entitlements not refreshed from the platform store" - Remediation: Users who reinstall your app and cannot access their paid content will file chargebacks and leave negative reviews. This is a significant post-launch business risk.
- Call the entitlement refresh on every app startup (not just after purchase):
// App.tsx or your app initialization useEffect(() => { const restoreEntitlements = async () => { if (isLoggedIn) { const customerInfo = await Purchases.getCustomerInfo(); setIsPremium(customerInfo.entitlements.active['premium'] !== undefined); } }; restoreEntitlements(); }, [isLoggedIn]); - For server-based entitlements, fetch subscription status from your backend at login and on app foreground events
- Call the entitlement refresh on every app startup (not just after purchase):
External references
- cwe · CWE-311 — Missing Encryption of Sensitive Data
- iso-25010:2011 · reliability.recoverability — ISO 25010 — Reliability / Recoverability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from app-store-iap-subscriptions·automated