# Permission rationale provided before request prompts

- **Pattern:** `ab-001894` (`mobile-permissions-privacy.permission-requests.permission-rationale`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001894
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001894)

## Why it matters

Users who see an unexpected system permission dialog — with no context about why an app needs access — deny at significantly higher rates than users who receive an explanation first. iOS only allows one native permission request per permission type: if the user denies the cold prompt, the app can never ask again via the system dialog. GDPR Art. 7 requires consent to be freely given, informed, and specific; a system dialog with no preceding rationale does not satisfy the informed requirement. Google Play's permission UX guidelines similarly require that the app explain the purpose before the native prompt fires. A denied permission that could have been granted represents a permanently lost capability — the user must manually navigate to Settings to re-enable it.

## Severity rationale

High because a denied permission from a cold, unexplained prompt is permanent on iOS and permanently degrades the feature — the only recovery requires the user to find and change a system-level setting.

## Remediation

Display an in-app explanation modal before every sensitive permission request. Keep the rationale specific to the feature the user is about to use.

```typescript
import { Alert } from 'react-native'
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions'

async function requestMicrophoneWithRationale() {
  await new Promise<void>(resolve =>
    Alert.alert(
      'Microphone Access',
      'Voice messages require microphone access. We never record without your action.',
      [{ text: 'Continue', onPress: resolve }]
    )
  )
  const result = await request(PERMISSIONS.IOS.MICROPHONE)
  return result === RESULTS.GRANTED
}
```

The rationale must reference the specific feature ("voice messages"), not vague purpose ("improve your experience"). For Android, set `rationale` directly in the `request()` call options — the library surfaces it via Android's built-in rationale dialog.

## Detection

- **ID:** `permission-rationale`
- **Severity:** `high`
- **What to look for:** Count all sensitive permission request calls (camera, location, contacts, calendar, photos, microphone). For each, check whether explanatory text, an alert, or a rationale screen is shown before the native permission prompt. Quote the actual rationale text found (or note its absence).
- **Pass criteria:** At least 100% of sensitive permission requests have a rationale shown before the native prompt. The rationale explains why the permission is needed in context of the feature. Report the ratio: "X of Y sensitive permission requests include rationale."
- **Fail criteria:** Any sensitive permission request prompt appears with no prior explanation, leaving users confused about why the app needs access.
- **Skip (N/A) when:** App requests no sensitive permissions (no camera, location, contacts, calendar, photos, or microphone).
- **Detail on fail:** Name the permissions that lack rationale. Quote the request call. Example: `"Camera permission is requested with no explanation shown to user"` or `"Contact list permission request appears with no prior rationale screen"`
- **Remediation:** Users are more likely to grant permissions when they understand why they're needed. Show a brief explanation before each permission prompt:

  ```typescript
  import { request, PERMISSIONS, RESULTS } from 'react-native-permissions'

  async function requestCameraWithRationale() {
    // Show explanation first
    Alert.alert(
      'Camera Access',
      'We need camera access to enable video calls',
      [
        { text: 'Cancel', onPress: () => {} },
        {
          text: 'Grant',
          onPress: async () => {
            const result = await request(PERMISSIONS.IOS.CAMERA)
            if (result === RESULTS.GRANTED) {
              // camera granted
            }
          },
        },
      ]
    )
  }
  ```

## External references

- gdpr Art. 7
- external google-play-permission-ux — https://developer.android.com/training/permissions/requesting#explain

Taxons: privacy-consent, user-experience

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