# Purchase entitlements survive app reinstall and device transfer

- **Pattern:** `ab-000407` (`app-store-iap-subscriptions.restore-entitlements.reinstall-survival`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000407
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000407)

## 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.

```tsx
// 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: `AsyncStorage` only (React Native), `SharedPreferences` only (Android), `UserDefaults` only (iOS), `MMKV` only — 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/Qonversion `customerInfo` fetched fresh on app launch — these query Apple/Google servers and survive reinstall; (3) StoreKit 2's `Transaction.currentEntitlements` sequence (async stream) — queries the App Store on-demand and survives reinstall. Search for a startup sequence that restores entitlements: `Purchases.getCustomerInfo()` in `App.tsx` or equivalent initialization, a server call like `GET /api/user/subscription` or `GET /api/entitlements` in the auth/startup flow, or `for await (let transaction in Transaction.currentEntitlements)` in Swift. Fail pattern: premium flag written to `AsyncStorage` at purchase time and read from `AsyncStorage` at 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 2 `currentEntitlements`) 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.
  1. Call the entitlement refresh on every app startup (not just after purchase):
     ```tsx
     // 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]);
     ```
  2. For server-based entitlements, fetch subscription status from your backend at login and on app foreground events

## External references

- cwe CWE-311
- iso-25010 reliability.recoverability

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000407
