App Lock Security
DWallet includes comprehensive security features to protect user financial data through PIN-based authentication, biometric support, and intelligent auto-lock mechanisms.
Features
Section titled “Features”- PIN Authentication: 4-digit PIN for quick access
- Biometric Support: Fingerprint and Face ID (device dependent)
- Auto-Lock: Automatic lock after app goes to background
- Grace Period: 30-second grace period for quick app switching
- Lockout Protection: 5 failed attempts triggers 30-second lockout
- Shake Animation: Visual feedback on incorrect PIN
Architecture
Section titled “Architecture”SecurityNotifier
Section titled “SecurityNotifier”File: lib/app/providers/security_provider.dart
Central state management for security features:
class SecurityNotifier extends Notifier<SecurityState> { static const int _gracePeriodSeconds = 30;
@override SecurityState build() { final storage = ref.read(securityStorageServiceProvider); // Load saved state from storage return SecurityState( isAppLockEnabled: storage.isAppLockEnabled(), isBiometricsEnabled: storage.isBiometricsEnabled(), hasPin: storage.getPin() != null, isLocked: shouldLockOnStart, ); }
Future<void> setPin(String pin) async { // Save PIN and enable app lock }
bool verifyPin(String enteredPin) { // Verify entered PIN against stored PIN }
void lock() { // Lock the app immediately }
void unlock() { // Unlock the app }
void onAppBackground() { // Called when app goes to background }
bool onAppForeground() { // Called when app returns to foreground // Returns true if app should be locked }}LockoutNotifier
Section titled “LockoutNotifier”File: lib/app/providers/lockout_provider.dart
Manages failed attempt tracking:
class LockoutNotifier extends Notifier<LockoutState> { static const int maxAttempts = 5; static const int lockoutDurationSeconds = 30;
void recordFailedAttempt() { // Increment failed attempts // Start lockout if max reached }
void reset() { // Clear failed attempts }}AppLockLifecycleObserver
Section titled “AppLockLifecycleObserver”File: lib/app/core/_app_lifecycle_observer.dart
Watches app lifecycle for background/foreground transitions:
class AppLockLifecycleObserver extends ConsumerStatefulWidget { @override void didChangeAppLifecycleState(AppLifecycleState state) { final notifier = ref.read(securityProvider.notifier);
switch (state) { case AppLifecycleState.paused: case AppLifecycleState.inactive: notifier.onAppBackground(); break; case AppLifecycleState.resumed: notifier.onAppForeground(); break; } }}Lock Screen UI
Section titled “Lock Screen UI”File: lib/app/widgets/_app_lock_screen.dart
The lock screen features:
- Large PIN indicators
- Shake animation on error
- Biometric button (if enabled)
- Lockout countdown display
class AppLockScreen extends ConsumerStatefulWidget { // Full-screen overlay with: // - Lock icon // - PIN indicators (4 dots) // - Numeric keypad // - Biometric button // - Error messages}Integration
Section titled “Integration”In Main App
Section titled “In Main App”File: lib/main.dart
The lock screen is shown as an overlay when needed:
class DWalletApp extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showLockScreen = ref.watch(showLockScreenProvider);
return ShadApp.router( builder: (context, child) { return Stack( children: [ child!, if (showLockScreen) const AppLockScreen(), // Overlay ], ); }, ); }}Settings UI
Section titled “Settings UI”File: lib/app/pages/client/settings/pages/app_lock_security/app_lock_security_view.dart
Settings page for configuring security:
- Toggle app lock on/off
- Set/change PIN
- Enable/disable biometrics
- View security status
Security Flows
Section titled “Security Flows”Setting Up PIN
Section titled “Setting Up PIN”1. User navigates to Settings > App Lock Security2. Taps "Set PIN"3. Enters 4-digit PIN4. Confirms PIN5. PIN saved to secure storage6. App lock enabled automaticallyUnlocking App
Section titled “Unlocking App”1. App starts or returns from background (>30s)2. Lock screen overlay appears3. User enters PIN or uses biometrics4. If correct: App unlocks5. If incorrect: Failed attempt recorded6. After 5 failures: 30-second lockoutBackground Behavior
Section titled “Background Behavior”App goes to background ↓Timestamp saved ↓App returns to foreground ↓Check elapsed time ↓< 30 seconds: No lock> 30 seconds: Show lock screenStorage
Section titled “Storage”Security data is stored using SecurityStorageService:
class SecurityStorageService { Future<void> savePin(String pin) async; String? getPin(); Future<void> setAppLockEnabled(bool enabled) async; bool isAppLockEnabled(); Future<void> setBiometricsEnabled(bool enabled) async; bool isBiometricsEnabled();}Note: PIN is stored as plaintext in SharedPreferences. For production apps requiring higher security, consider using flutter_secure_storage with encryption.
Biometric Authentication
Section titled “Biometric Authentication”Uses local_auth package:
class BiometricService { Future<bool> authenticate() async { return await _localAuth.authenticate( localizedReason: 'Please authenticate to access the app', biometricOnly: true, ); }
Future<bool> canCheckBiometrics() async { return await _localAuth.canCheckBiometrics && await _localAuth.isDeviceSupported(); }}Android Configuration
Section titled “Android Configuration”File: android/app/src/main/kotlin/.../MainActivity.kt
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity() // Required for local_authFile: android/app/src/main/AndroidManifest.xml
<application android:enableOnBackInvokedCallback="true" ...>Customization
Section titled “Customization”Change Grace Period
Section titled “Change Grace Period”Edit SecurityNotifier:
static const int _gracePeriodSeconds = 60; // Change to 1 minuteChange Max Attempts
Section titled “Change Max Attempts”Edit LockoutNotifier:
static const int maxAttempts = 3; // Stricter policystatic const int lockoutDurationSeconds = 60; // Longer lockoutCustom Lock Screen
Section titled “Custom Lock Screen”Extend AppLockScreen or modify _app_lock_screen.dart:
class CustomLockScreen extends AppLockScreen { @override Widget build(BuildContext context) { // Your custom UI }}Best Practices
Section titled “Best Practices”- Always lock on fresh start: If app lock is enabled, lock immediately
- Respect grace period: Don’t lock if user quickly switches apps
- Clear sensitive data: Clear PIN input on each attempt
- Visual feedback: Show shake animation on incorrect PIN
- Biometric fallback: Always offer PIN as fallback to biometrics
- Secure storage: Use encrypted storage for production apps
- Rate limiting: Implement lockout after failed attempts
Troubleshooting
Section titled “Troubleshooting”Biometrics Not Working
Section titled “Biometrics Not Working”- Check device supports biometrics
- Ensure biometrics are enrolled in device settings
- Verify
FlutterFragmentActivityis used - Check Android manifest has proper permissions
Lock Screen Not Showing
Section titled “Lock Screen Not Showing”- Verify
AppLockLifecycleObserverwraps the app - Check
showLockScreenProviderlogic - Ensure
isAppLockEnabledis true - Verify PIN has been set