Introduction
Security breaches can destroy user trust and your app’s reputation. This guide shows you practical steps to protect your Flutter apps and user data, using real examples from production apps.
1. Essential Security Features in Flutter
Why Flutter is Secure By Default
- Type Safety: Catches coding mistakes early
// This won't compile - Flutter prevents this error
String name = 42; // Error: A value of type 'int' can't be assigned to a variable of type 'String'
// This is safe
String name = "John";
- Memory Protection: Prevents common memory attacks
// Flutter automatically handles memory
// You don't need to manually allocate/free memory like in C++
void someFunction() {
var list = List.filled(1000000, "test");
// Memory is automatically cleaned when not needed
}
- Compiled Code: Makes your app harder to hack
# In build.gradle, enable R8 for better code protection
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
2. Real Security Problems and Solutions
Problem 1: Storing Sensitive Data
class SecureStorage {
final storage = FlutterSecureStorage();
// Bad - Don't do this
Future<void> unsafeStore(String creditCard) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('credit_card', creditCard); // Stored in plain text!
}
// Good - Do this instead
Future<void> safeStore(String creditCard) async {
await storage.write(
key: 'credit_card',
value: creditCard,
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
keyCipher: StorageCipher.AES_CBC_PKCS7Padding,
),
iOptions: IOSOptions(
accessibility: IOSAccessibility.first_unlock,
),
);
}
}
Problem 2: Unsafe Network Calls
class ApiService {
late Dio _dio;
ApiService() {
_dio = Dio()
..options.baseUrl = 'https://api.yourapp.com'
..options.connectTimeout = Duration(seconds: 5)
..options.receiveTimeout = Duration(seconds: 3)
..interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// Add security headers
options.headers['X-API-Key'] = const String.fromEnvironment('API_KEY');
return handler.next(options);
},
onError: (error, handler) {
// Handle errors securely
if (error.response?.statusCode == 401) {
// Handle unauthorized access
logout();
}
return handler.next(error);
},
),
);
}
// Example secure API call
Future<UserData> fetchUserData(String userId) async {
try {
final response = await _dio.get('/users/$userId');
return UserData.fromJson(response.data);
} catch (e) {
// Log error securely
await SecureLogger.logError(e);
throw SecurityException('Failed to fetch user data');
}
}
}
Problem 3: User Authentication
class SecureAuth {
// Check if device is secure
Future<bool> isDeviceSecure() async {
final localAuth = LocalAuthentication();
return await localAuth.canCheckBiometrics ||
await localAuth.isDeviceSupported();
}
// Secure login with biometrics
Future<bool> authenticateUser() async {
try {
final localAuth = LocalAuthentication();
return await localAuth.authenticate(
localizedReason: 'Please authenticate to access the app',
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
} catch (e) {
await SecureLogger.logError(e);
return false;
}
}
// Secure session management
Future<void> startSecureSession() async {
final sessionToken = await generateSecureToken();
await SecureStorage().safeStore('session_token', sessionToken);
// Start session timeout
Timer(Duration(minutes: 30), () {
logout();
});
}
}
3. Protecting Against Common Attacks
SQL Injection Protection
class DatabaseService {
// Bad - Vulnerable to SQL injection
Future<void> unsafeQuery(String userInput) async {
await db.rawQuery("SELECT * FROM users WHERE name = '$userInput'");
}
// Good - Safe from SQL injection
Future<void> safeQuery(String userInput) async {
await db.query(
'users',
where: 'name = ?',
whereArgs: [userInput],
);
}
}
XSS Protection
class HtmlSanitizer {
static String sanitize(String html) {
// Remove dangerous HTML tags and attributes
return html
.replaceAll(RegExp(r'<script[^>]*>.*?</script>'), '')
.replaceAll(RegExp(r'on\w+=".*?"'), '')
.replaceAll(RegExp(r'javascript:'), '');
}
}
4. Security Testing
Basic Security Tests
void main() {
group('Security Tests', () {
test('should securely store sensitive data', () async {
final storage = SecureStorage();
await storage.safeStore('test_key', 'sensitive_data');
// Verify data is encrypted
final file = File('path_to_storage');
final content = await file.readAsString();
expect(content.contains('sensitive_data'), false);
});
test('should prevent unauthorized access', () async {
final auth = SecureAuth();
final result = await auth.authenticateUser();
// Verify authentication is required
expect(result, false);
});
});
}
5. Security Checklist for Production
Before Release
- ✅ Enable code obfuscation
- ✅ Implement certificate pinning
- ✅ Set up crash reporting
- ✅ Add API key protection
- ✅ Test on real devices
Regular Maintenance
- 🔄 Update dependencies monthly
- 🔄 Review security logs
- 🔄 Test security features
- 🔄 Update encryption keys
- 🔄 Audit user permissions
Conclusion
Remember:
- Security is ongoing - keep updating and testing
- Use Flutter’s built-in security features
- Always encrypt sensitive data
- Test security features regularly
- Keep dependencies updated
A secure app is a successful app. Take security seriously from day one.