A permission denial is a user's explicit choice to restrict access — an app that crashes in response to that choice punishes the user for exercising their privacy preference. This is both a UX failure and a reliability issue captured by CWE-391 (Unchecked Error Condition) and ISO 25010 reliability fault-tolerance requirements. On iOS, once a user denies a permission, the native dialog never appears again — the app must handle the BLOCKED state and guide the user to Settings. An app that shows a generic error message with no Settings deep-link leaves the user with no path forward. The pattern of crashing on denial also causes 1-star reviews specifically citing privacy concerns, compounding the trust damage.
Medium because permission-denial crashes create immediate user-facing failures and, for users exercising GDPR consent withdrawal, punish a legally protected user action with an application crash.
Handle all three permission states — GRANTED, DENIED, BLOCKED — for every permission-gated feature. For BLOCKED state, always provide a direct Settings deep-link.
import { request, check, PERMISSIONS, RESULTS, openSettings } from 'react-native-permissions'
async function activateCameraFeature() {
const status = await check(
Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA
)
switch (status) {
case RESULTS.GRANTED:
startCamera()
break
case RESULTS.DENIED:
const result = await request(
Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA
)
if (result === RESULTS.GRANTED) startCamera()
break
case RESULTS.BLOCKED:
Alert.alert(
'Camera Access Required',
'Camera access was denied. Open Settings to enable it.',
[{ text: 'Open Settings', onPress: openSettings }, { text: 'Cancel' }]
)
break
default:
// Feature unavailable — disable UI element, do not crash
setFeatureAvailable(false)
}
}
ID: mobile-permissions-privacy.graceful-degradation.permission-denial-handling
Severity: medium
What to look for: List all permission-gated features. For each, check whether the code handles at least 3 permission states: GRANTED, DENIED, and BLOCKED. Look for try-catch blocks, null checks, or fallback UI for each denied/blocked path.
Pass criteria: At least 100% of permission-gated features handle denial gracefully — showing a user-friendly message or disabling the feature without crashing. Each feature handles at least 2 denial states (DENIED and BLOCKED).
Fail criteria: Any feature crashes or behaves unexpectedly when a permission is denied. OR displays a generic error with no guidance on how to grant the permission.
Skip (N/A) when: App requests no permissions (no permission-gated features exist).
Detail on fail: Quote the error handling code (or note its absence). Example: "Camera permission denied causes app to crash in video-call component" or "Location permission denied shows generic 'error' message with no guidance"
Cross-reference: For error handling patterns and user-facing error recovery UX, the Error Resilience audit covers error boundaries and graceful degradation.
Remediation: Handle permission denials gracefully:
async function startVideoCall() {
const result = await request(PERMISSIONS.IOS.CAMERA)
if (result === RESULTS.GRANTED) {
// Start call
} else if (result === RESULTS.DENIED) {
Alert.alert('Camera Required', 'Enable camera access in Settings to make video calls')
} else if (result === RESULTS.BLOCKED) {
Alert.alert(
'Camera Disabled',
'You previously denied camera access. Go to Settings > [AppName] > Camera and toggle it on.',
[
{ text: 'Cancel' },
{ text: 'Go to Settings', onPress: () => openSettings() }
]
)
}
}