Flutter Interview Series

Master the security concepts every Flutter developer needs to know — from runtime permissions to secure data storage

Mobile app security is no longer optional — it's a fundamental expectation. In Flutter interviews, questions about permissions and security basics reveal whether a candidate understands how to build apps that protect user data and comply with platform guidelines.

This guide covers the most frequently asked interview questions on this topic, with clear, actionable answers you can use to demonstrate your expertise.

Q1: How do you handle runtime permissions in Flutter?

Sample Answer:

Flutter doesn't have a built-in permission API, so we use the permission_handler package to manage runtime permissions across iOS and Android.

import 'package:permission_handler/permission_handler.dart';

Future<void> requestCameraPermission() async {
  final status = await Permission.camera.status;
  
  if (status.isDenied) {
    final result = await Permission.camera.request();
    
    if (result.isGranted) {
      // Proceed with camera access
    } else if (result.isPermanentlyDenied) {
      // Guide user to app settings
      await openAppSettings();
    }
  }
}

Key points to mention:

  • Always check permission status before requesting
  • Handle permanentlyDenied state by directing users to system settings
  • Request permissions contextually, not at app launch
  • On iOS, you must add usage descriptions in Info.plist
  • On Android 13+, granular media permissions replaced READ_EXTERNAL_STORAGE

Q2: What's the difference between iOS and Android permission models?

Sample Answer:

The two platforms handle permissions differently:

Android:

  • Permissions declared in AndroidManifest.xml
  • Dangerous permissions require runtime requests
  • Users can revoke permissions anytime from settings
  • Android 11+ introduced one-time permissions for location, camera, and microphone
  • Android 14 added partial photo access

iOS:

  • All sensitive permissions require runtime authorization
  • Usage description strings are mandatory in Info.plist
  • iOS offers "Allow Once" for location
  • iOS 14+ requires ATT (App Tracking Transparency) for cross-app tracking
  • Photo library offers "Limited Access" option
<!-- iOS Info.plist -->
<key>NSCameraUsageDescription</key>
<string>We need camera access to take profile photos</string>

<!-- Android AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA"/>

Q3: How do you securely store sensitive data in Flutter?

Sample Answer:

For sensitive data like tokens, API keys, or user credentials, I use flutter_secure_storage, which leverages platform-native secure storage mechanisms.

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  final _storage = const FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: KeychainAccessibility.first_unlock_this_device,
    ),
  );

  Future<void> saveToken(String token) async {
    await _storage.write(key: 'auth_token', value: token);
  }

  Future<String?> getToken() async {
    return await _storage.read(key: 'auth_token');
  }

  Future<void> deleteToken() async {
    await _storage.delete(key: 'auth_token');
  }
}

Why this matters:

  • On iOS, data is stored in the Keychain
  • On Android, it uses EncryptedSharedPreferences (API 23+)
  • Never use SharedPreferences for sensitive data — it stores plain text
  • Consider KeychainAccessibility options for iOS background access requirements

Q4: How do you secure network communications in a Flutter app?

Sample Answer:

Securing network traffic involves multiple layers:

  1. Enforce HTTPS:
// Dio configuration with security options
final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 10),
));

2. Implement Certificate Pinning:

// Using dio with certificate pinning
import 'dart:io';

class ApiClient {
  Dio createSecureDio() {
    final dio = Dio();
    
    (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
      final client = HttpClient();
      client.badCertificateCallback = (cert, host, port) {
        // Compare certificate fingerprint
        final expectedFingerprint = 'YOUR_CERT_FINGERPRINT';
        final certFingerprint = sha256.convert(cert.der).toString();
        return certFingerprint == expectedFingerprint;
      };
      return client;
    };
    
    return dio;
  }
}

3. Configure platform-specific network security:

For Android, create network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.example.com</domain>
    </domain-config>
</network-security-config>

Q5: What is obfuscation and why is it important?

Sample Answer:

Obfuscation makes reverse engineering more difficult by renaming classes, methods, and variables to meaningless names.

Enable obfuscation for release builds:

flutter build apk --obfuscate --split-debug-info=build/debug-info
flutter build ios --obfuscate --split-debug-info=build/debug-info

Why it matters:

  • Protects business logic from competitors
  • Makes it harder to find security vulnerabilities
  • Reduces APK/IPA size slightly
  • Required by many enterprise security policies

Important: Always save the symbol files from --split-debug-info — you'll need them to decode crash reports.

Q6: How do you protect API keys in a Flutter application?

Sample Answer:

API keys should never be hardcoded in source code. Here's my approach:

  1. Use compile-time environment variables:
// Define during build
// flutter build apk --dart-define=API_KEY=your_key_here

class AppConfig {
  static const apiKey = String.fromEnvironment('API_KEY');
}

2. For additional security, use native platform storage:

// Android: Store in local.properties (not committed to git)
// Access via BuildConfig or native method channel

3. Consider backend proxy for highly sensitive keys:

  • Don't embed third-party API keys in the app
  • Route requests through your backend
  • Your server adds the API key server-side

What NOT to do:

  • Don't commit .env files with real keys
  • Don't store keys in assets/ folder
  • Don't rely solely on obfuscation — determined attackers can still extract strings

Q7: How do you implement biometric authentication?

Sample Answer:

The local_auth package provides biometric authentication for both platforms:

import 'package:local_auth/local_auth.dart';

class BiometricAuthService {
  final _localAuth = LocalAuthentication();

  Future<bool> isBiometricAvailable() async {
    final canCheck = await _localAuth.canCheckBiometrics;
    final isDeviceSupported = await _localAuth.isDeviceSupported();
    return canCheck && isDeviceSupported;
  }

  Future<List<BiometricType>> getAvailableBiometrics() async {
    return await _localAuth.getAvailableBiometrics();
  }

  Future<bool> authenticate() async {
    try {
      return await _localAuth.authenticate(
        localizedReason: 'Authenticate to access your account',
        options: const AuthenticationOptions(
          stickyAuth: true,
          biometricOnly: false, // Allow PIN/pattern as fallback
        ),
      );
    } on PlatformException catch (e) {
      // Handle errors (no biometrics enrolled, etc.)
      return false;
    }
  }
}

Configuration required:

For iOS, add to Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to securely log you in</string>

For Android, add to AndroidManifest.xml:

<uses-permission android:name="android.permission.USE_BIOMETRIC"/>

Q8: How do you prevent screenshot capture for sensitive screens?

Sample Answer:

This requires platform-specific implementation via method channels:

Android (MainActivity.kt):

import android.view.WindowManager

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "app/security")
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "enableSecureMode" -> {
                        window.setFlags(
                            WindowManager.LayoutParams.FLAG_SECURE,
                            WindowManager.LayoutParams.FLAG_SECURE
                        )
                        result.success(true)
                    }
                    "disableSecureMode" -> {
                        window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
                        result.success(true)
                    }
                    else -> result.notImplemented()
                }
            }
    }
}

Flutter side:

class SecurityService {
  static const _channel = MethodChannel('app/security');

  static Future<void> enableSecureMode() async {
    await _channel.invokeMethod('enableSecureMode');
  }

  static Future<void> disableSecureMode() async {
    await _channel.invokeMethod('disableSecureMode');
  }
}

Note: iOS doesn't provide a direct equivalent. You can detect screenshots using UIApplication.userDidTakeScreenshotNotification but cannot prevent them.

Q9: What security considerations apply to deep links?

Sample Answer:

Deep links can be attack vectors if not properly validated:

  1. Validate all deep link parameters:
class DeepLinkHandler {
  void handleDeepLink(Uri uri) {
    // Never trust deep link data
    final userId = uri.queryParameters['userId'];
    
    if (userId != null && _isValidUserId(userId)) {
      // Additional authorization check
      if (_currentUser.canAccessUser(userId)) {
        _navigateToUserProfile(userId);
      }
    }
  }

  bool _isValidUserId(String id) {
    // Validate format, length, characters
    return RegExp(r'^[a-zA-Z0-9]{8,32}$').hasMatch(id);
  }
}

2. Use App Links (Android) and Universal Links (iOS):

  • These require server-side verification files
  • They prevent other apps from hijacking your deep links
  • More secure than custom URL schemes

3. Never include sensitive data in deep links:

  • No tokens or passwords
  • No personally identifiable information
  • Use short-lived, one-time codes if needed

Q10: How do you detect if an app is running on a rooted/jailbroken device?

Sample Answer:

While no detection is foolproof, you can implement basic checks using packages like flutter_jailbreak_detection:

import 'package:flutter_jailbreak_detection/flutter_jailbreak_detection.dart';

class DeviceSecurityService {
  Future<bool> isDeviceCompromised() async {
    final isJailbroken = await FlutterJailbreakDetection.jailbroken;
    final isDeveloperMode = await FlutterJailbreakDetection.developerMode;
    
    return isJailbroken || isDeveloperMode;
  }

  Future<void> enforceSecurityPolicy() async {
    if (await isDeviceCompromised()) {
      // Options:
      // 1. Show warning but allow usage
      // 2. Disable sensitive features
      // 3. Prevent app usage entirely (banking apps)
    }
  }
}

Important considerations:

  • Detection methods can be bypassed by sophisticated tools
  • Consider your app's risk profile — banking apps need stricter policies
  • Some legitimate users have rooted devices for valid reasons
  • Provide clear messaging about why the restriction exists

Key Takeaways for Your Interview

  1. Always request permissions contextually — explain why you need access right when it's needed
  2. Use platform-appropriate secure storage — Keychain for iOS, EncryptedSharedPreferences for Android
  3. Never hardcode secrets — use environment variables or backend proxies
  4. Defense in depth — combine obfuscation, certificate pinning, and input validation
  5. Stay current — permission models evolve with each OS version

Security isn't a feature you add at the end — it's a mindset that should influence every architectural decision you make.

#Flutter #FlutterDeveloper #MobileAppSecurity #FlutterInterview #AppPermissions #FlutterSecurity #MobileDevelopment #TechInterview #iOSDevelopment #AndroidDevelopment #SecureCoding #FlutterTips