# ATT framework requested before any tracking identifier is read

- **Pattern:** `ab-000472` (`app-store-privacy-data.tracking-advertising.att-before-tracking`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000472
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000472)

## Why it matters

Apple's ATT framework (external apple-att-framework) requires explicit user authorisation before any app reads the IDFA or allows tracking SDKs to access cross-app identifiers. Initialising AdMob, Meta, or AppsFlyer in `AppDelegate.application(_:didFinishLaunchingWithOptions:)` before the ATT prompt resolves means those SDKs read IDFA before consent — a direct violation of Apple's policy, GDPR Art.7 (consent must precede processing), and CCPA §1798.120 (right to opt out of sale/sharing). Apple has rejected apps for this pattern since iOS 14.5 and continues to enforce it on every submission. Missing `NSUserTrackingUsageDescription` in `Info.plist` causes the app to crash when the ATT dialog is triggered.

## Severity rationale

Critical because ATT violations cause immediate binary rejection, and reading IDFA before consent is grounds for enforcement action by Apple and regulators under GDPR Art.7.

## Remediation

Request ATT authorisation before initialising any SDK that reads device identifiers. Trigger the prompt in `applicationDidBecomeActive`, not `didFinishLaunchingWithOptions`:

```swift
import AppTrackingTransparency

func applicationDidBecomeActive(_ application: UIApplication) {
  ATTrackingManager.requestTrackingAuthorization { status in
    DispatchQueue.main.async {
      switch status {
      case .authorized:
        GADMobileAds.sharedInstance().start()
      default:
        GADMobileAds.sharedInstance().start() // limited mode
      }
    }
  }
}
```

For Expo/React Native, use `expo-tracking-transparency` and `await requestTrackingPermissionsAsync()` before any SDK initialisation. Add `NSUserTrackingUsageDescription` to `Info.plist` with a specific description of the tracking purpose.

## Detection

- **ID:** `att-before-tracking`
- **Severity:** `critical`
- **What to look for:** Count all relevant instances and enumerate each. Search for all locations where tracking identifiers or tracking-related APIs are accessed on iOS: `ASIdentifierManager.sharedManager().advertisingIdentifier` (IDFA); `SKAdNetwork` attribution calls; third-party SDK initializations that read IDFA internally (AdMob `GADMobileAds.sharedInstance().start()`, Meta `FBSDKApplicationDelegate.sharedInstance().application(...)`, AppsFlyer `AppsFlyerLib.shared().start()`). Then find where `ATTrackingManager.requestTrackingAuthorization(completionHandler:)` is called (or `requestPermissionsAsync()` from `expo-tracking-transparency`). Verify the temporal ordering: ATT authorization must be requested and a result received BEFORE any tracking SDK is initialized or IDFA is read. Look for patterns where SDKs are initialized in `AppDelegate.application(_:didFinishLaunchingWithOptions:)` or a top-level `useEffect` BEFORE the ATT prompt appears. Also check: is `NSUserTrackingUsageDescription` set in `Info.plist`? (Required — without it, the ATT dialog crashes the app on iOS 14+.) Does the app handle the `.denied` and `.restricted` cases by switching to a limited-data or contextual-ads-only mode?
- **Pass criteria:** ATT authorization is requested before any tracking SDK reads a device identifier. At least 1 implementation must be verified. The app handles all four ATT status cases (authorized, denied, restricted, notDetermined) gracefully. `NSUserTrackingUsageDescription` is set with a specific explanation.
- **Fail criteria:** Tracking SDKs initialized before ATT authorization is received; IDFA accessed without an ATT prompt; `NSUserTrackingUsageDescription` missing; ATT denial not handled (app crashes or breaks tracking-dependent features).
- **Skip (N/A) when:** App has no advertising or tracking SDKs and does not read IDFA or any cross-app tracking identifier. (Note: apps that only use analytics for first-party measurement without cross-app tracking may not need ATT — but confirm no IDFA is read.)
- **Detail on fail:** `"GADMobileAds.sharedInstance().start() called in AppDelegate before ATTrackingManager.requestTrackingAuthorization — AdMob reads IDFA before consent is collected"` or `"NSUserTrackingUsageDescription missing from Info.plist — app will crash when ATT dialog is triggered on iOS 14+"`
- **Remediation:** Violating ATT requirements is an immediate rejection and a potential privacy violation enforcement action from Apple.
  1. Request ATT before any SDK initialization that reads IDFA:
     ```swift
     // AppDelegate or app startup
     import AppTrackingTransparency

     func applicationDidBecomeActive(_ application: UIApplication) {
       ATTrackingManager.requestTrackingAuthorization { status in
         DispatchQueue.main.async {
           switch status {
           case .authorized:
             // Initialize tracking SDKs
             GADMobileAds.sharedInstance().start()
           default:
             // Initialize SDKs in limited/contextual mode
             GADMobileAds.sharedInstance().start()
             // Ensure IDFA is not passed
           }
         }
       }
     }
     ```
  2. For Expo/React Native, use `expo-tracking-transparency`:
     ```ts
     import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
     const { status } = await requestTrackingPermissionsAsync();
     // Initialize SDKs after this resolves
     ```
  3. Request ATT in `applicationDidBecomeActive` (not `didFinishLaunchingWithOptions`) to ensure the dialog appears at the right time

## External references

- external apple-att-framework — https://developer.apple.com/documentation/apptrackingtransparency
- cwe CWE-359
- gdpr Art.7
- ccpa §1798.120

Taxons: privacy-consent, regulatory-conformance

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