guardo 0.1.0 copy "guardo: ^0.1.0" to clipboard
guardo: ^0.1.0 copied to clipboard

A Flutter package that provides secure app entry point with auto-locking, biometric authentication, and lifecycle management.

Guardo #

A powerful Flutter package for biometric authentication with lock screen functionality

Pub Version License: MIT Flutter

Guardo provides a seamless way to add biometric authentication and app locking functionality to your Flutter applications. With automatic lockout handling, customizable lock screens, and comprehensive error management, it's the perfect solution for securing sensitive apps.


Table of Contents #


Features #

Authentication #

  • Biometric Authentication - Fingerprint, Face ID, and other biometric methods
  • Device Credentials Fallback - Automatic fallback to PIN/Pattern/Password when biometrics are locked out
  • Automatic Lockout Handling - Smart handling of temporary and permanent biometric lockouts
  • Smart Availability Detection - Automatically detects when authentication methods are unavailable
  • Authentication Bypass - Gracefully bypasses authentication when no security credentials are available
  • Enable/Disable Control - Complete control to enable or disable authentication programmatically

User Interface #

  • Customizable Lock Screens - Build your own lock screen or use the default
  • Multiple UI States - Loading, authenticated, locked, error, and failed states
  • Theme Support - Light and dark theme compatibility
  • Accessibility - Full screen reader support and WCAG compliance

Configuration #

  • Auto-lock Timer - Configurable inactivity timeout
  • Authentication Options - Flexible biometric and credential settings
  • App Lifecycle Management - Proper handling of app resume/pause states
  • Sticky Authentication - Keep authentication active across app launches

Developer Experience #

  • Extension Methods - Convenient BuildContext extensions for easy integration
  • Comprehensive Error Handling - Typed exceptions with detailed error information
  • State Management - Built-in state notifier with reactive updates
  • Debug Support - Extensive logging and debugging capabilities
  • Flexible Configuration - Easy enable/disable for testing, debugging, or different builds
  • Device Capability Checking - Built-in methods to check authentication availability

What's New in v0.1.0 🎉 #

🔍 Smart Authentication Detection #

Guardo now automatically detects when authentication methods are unavailable and gracefully bypasses authentication, ensuring your app works on all devices.

// Automatically handles devices without biometrics
Guardo(
  child: MyApp(), // Shows directly if no auth methods available
)

⚙️ Complete Control #

New enabled property allows you to programmatically enable or disable authentication completely.

Guardo(
  enabled: kDebugMode ? false : true, // Disable in debug mode
  child: MyApp(),
)

🛠️ Enhanced Developer Experience #

New extension methods and better error handling for a smoother development experience.

// Check authentication availability
if (await context.hasAuthenticationMethods()) {
  await context.lockApp(); // Safe to lock
}

// Check if Guardo is enabled
if (context.isGuardoEnabled()) {
  // Authentication is active
}

🚀 Improved Reliability #

Better error handling and state management ensures your app works reliably across all device configurations.


Installation #

Add Guardo to your pubspec.yaml file:

dependencies:
  guardo: ^0.1.0

Platform Setup #

Android

Android Integration

  • The plugin will build and run on SDK 16+, but isDeviceSupported() will always return false before SDK 23 (Android 6.0).

Add the following permissions to android/app/src/main/AndroidManifest.xml:

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

Activity Changes

Note that local_auth requires the use of a FragmentActivity instead of an Activity. To update your application:

  • If you are using FlutterActivity directly, change it to FlutterFragmentActivity in your AndroidManifest.xml.

  • If you are using a custom activity, update your MainActivity.java:

import io.flutter.embedding.android.FlutterFragmentActivity;

public class MainActivity extends FlutterFragmentActivity {
    // ...
}

or MainActivity.kt:

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity() {
    // ...
}

to inherit from FlutterFragmentActivity.

iOS

Add the following to ios/Runner/Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID for secure authentication</string>

Then run:

flutter pub get

Quick Start #

Here's the simplest way to add biometric authentication to your app:

import 'package:flutter/material.dart';
import 'package:guardo/guardo.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Guardo(
      config: GuardoConfig(
        localizedReason: 'Please authenticate to access the app',
        lockTimeout: Duration(minutes: 5),
      ),
      child: MaterialApp(
        home: SecureHomePage(),
      ),
    );
  }
}

class SecureHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Secure App')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            context.secureAction(
              onSuccess: () => print('Secure action completed!'),
              onFailure: (error) => print('Authentication failed: $error'),
            );
          },
          child: Text('Perform Secure Action'),
        ),
      ),
    );
  }
}

Usage #

Basic Setup #

Wrap your app with the Guardo widget to enable authentication:

Guardo(
  // Enable/disable authentication completely
  enabled: true, // Set to false to bypass all authentication
  
  config: GuardoConfig(
    localizedReason: 'Please authenticate to access the app',
    biometricOnly: true,
    stickyAuth: true,
    lockTimeout: Duration(minutes: 5),
    autoCheckOnStart: true,
  ),
  onAuthenticationChanged: (isAuthenticated) {
    print('Auth state: $isAuthenticated');
  },
  child: YourApp(),
)

Configuration Options #

Configure Guardo behavior with GuardoConfig:

GuardoConfig(
  // Message shown to user during authentication
  localizedReason: 'Please authenticate to access the app',
  
  // Use only biometrics (no PIN/password fallback in normal flow)
  biometricOnly: true,
  
  // Keep authentication active between prompts
  stickyAuth: true,
  
  // Auto-lock after inactivity (null to disable)
  lockTimeout: Duration(minutes: 5),
  
  // Automatically check auth on app start
  autoCheckOnStart: true,
  
  // Advanced authentication options
  authenticationOptions: AuthenticationOptions(
    biometricOnly: true,
    stickyAuth: true,
  ),
)

Extension Methods #

Use convenient extension methods for common operations:

// Check authentication status
if (context.isAuthenticated) {
  // User is authenticated
}

// Check if Guardo is enabled/functional
if (context.isGuardoEnabled()) {
  // Authentication is active
}

// Lock the app manually (now async with error handling)
try {
  await context.lockApp();
} catch (e) {
  // Handle case where authentication is disabled/unavailable
  print('Cannot lock: $e');
}

// Unlock the app
final success = await context.unlockApp();

// Reset the auto-lock timer
context.resetLockTimer();

// Check device capabilities
final canAuth = await context.canAuthenticate();
final isSupported = await context.isDeviceSupported();
final biometrics = await context.getAvailableBiometrics();
final hasAuthMethods = await context.hasAuthenticationMethods();

// Perform secure actions
await context.secureAction(
  onSuccess: () => performSensitiveOperation(),
  onFailure: (error) => handleAuthError(error),
  reason: 'Custom authentication reason',
);

Custom Lock Screens #

Create your own lock screen design:

Guardo(
  lockScreen: (context, onUnlock) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.lock, size: 100),
            SizedBox(height: 20),
            Text('App Locked', style: TextStyle(fontSize: 24)),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: onUnlock,
              child: Text('Unlock'),
            ),
          ],
        ),
      ),
    );
  },
  child: YourApp(),
)

API Reference #

Guardo Widget #

The main widget that provides authentication functionality.

Property Type Description
child Widget The widget to show when authenticated
config GuardoConfig? Authentication configuration
lockScreen LockScreenBuilder? Custom lock screen builder
loadingWidget Widget? Custom loading widget
failedWidget Widget? Custom failed authentication widget
guardoService GuardoService? Custom service instance
onAuthenticationChanged Function(bool)? Authentication state callback
autoRetry bool Auto-retry on app resume (default: true)
enabled bool Enable/disable authentication completely (default: true)

GuardoConfig #

Configuration class for authentication behavior.

Property Type Default Description
localizedReason String 'Please authenticate...' User-facing auth reason
biometricOnly bool true Use only biometrics in normal flow
stickyAuth bool true Keep auth session active
lockTimeout Duration? null Auto-lock timeout
autoCheckOnStart bool true Check auth on app start
authenticationOptions AuthenticationOptions Biometric options Advanced auth settings

GuardoService #

Service class handling biometric authentication.

Methods

// Check device capabilities
Future<bool> get canCheckBiometrics
Future<bool> get isDeviceSupported
Future<List<BiometricType>> get availableBiometrics
Future<bool> get hasAuthenticationMethods  // New: Check if any auth methods are available

// Authentication
Future<bool> authenticate({bool allowFallback = false})
Future<bool> authenticateWithDeviceCredentials()
Future<bool> stopAuthentication()

// Configuration
GuardoConfig get config

GuardoExtensions #

Extension methods on BuildContext for convenient access.

Authentication Methods

// Secure actions
Future<void> secureAction({...})
Future<T?> guardoActionWithResult<T>({...})
Future<void> guardoAsyncAction({...})
Future<T?> guardoAsyncActionWithResult<T>({...})

// Device capabilities
Future<bool> canAuthenticate({GuardoService? guardoService})
Future<bool> isDeviceSupported({GuardoService? guardoService})
Future<List<BiometricType>> getAvailableBiometrics({GuardoService? guardoService})
Future<bool> hasAuthenticationMethods({GuardoService? guardoService})  // New

App Control Methods

// Lock/unlock
Future<void> lockApp()  // Now async with error handling
Future<bool> unlockApp()
void resetLockTimer()

// State checking
bool get isAuthenticated
bool get isAppLocked
bool isGuardoEnabled()  // New: Check if Guardo is enabled
GuardoState? get guardoState

Authentication States #

Guardo uses a sealed class hierarchy for state management:

sealed class GuardoState

class CheckingState extends GuardoState      // Checking authentication
class AuthenticatedState extends GuardoState // User authenticated
class LockScreenState extends GuardoState    // Showing lock screen
class BypassedState extends GuardoState      // Authentication bypassed (no methods available)
class ErrorState extends GuardoState         // Authentication error
class FailedState extends GuardoState        // Authentication failed

Error Handling #

Guardo provides a comprehensive exception hierarchy for detailed error handling:

Exception Types #

// Base exception class
abstract class GuardoException implements Exception

// Specific exception types
class BiometricLockoutException extends GuardoException
class BiometricUnavailableException extends GuardoException
class AuthenticationFailedException extends GuardoException

Handling Errors #

try {
  await context.secureAction(
    onSuccess: () => performSecureAction(),
  );
} on BiometricLockoutException catch (e) {
  // Biometrics temporarily disabled
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('Biometrics Locked'),
      content: Text('Too many failed attempts. Try again later.'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: Text('OK'),
        ),
      ],
    ),
  );
} on BiometricUnavailableException catch (e) {
  // Biometrics not available on device
  showSnackBar('Biometric authentication not available');
} on AuthenticationFailedException catch (e) {
  // General authentication failure
  showSnackBar('Authentication failed: ${e.message}');
}

Accessibility #

Guardo is built with accessibility in mind:

Features #

  • Screen Reader Support - All UI elements have semantic labels
  • High Contrast - Theme-aware colors for better visibility
  • Keyboard Navigation - Full keyboard support
  • Voice Control - Compatible with voice control systems

Customization #

Add semantic labels to your custom lock screens:

lockScreen: (context, onUnlock) {
  return Scaffold(
    body: Semantics(
      label: 'Application lock screen',
      child: Center(
        child: Column(
          children: [
            Semantics(
              label: 'Lock icon',
              child: Icon(Icons.lock, size: 100),
            ),
            ElevatedButton(
              onPressed: onUnlock,
              child: Text('Unlock App'),
            ),
          ],
        ),
      ),
    ),
  );
}

Platform Support #

Platform Biometric Types Requirements
iOS Face ID, Touch ID iOS 9.0+, Xcode 12+
Android Fingerprint, Face, Iris Android API 23+

Supported Biometric Types #

enum BiometricType {
  face,      // Face recognition
  fingerprint, // Fingerprint
  iris,      // Iris scanning
  weak,      // Device credentials (PIN/Pattern)
  strong,    // Strong biometrics
}

Actions & Use Cases #

Guardo provides various action methods to handle different authentication scenarios. Each action type is designed for specific use cases with built-in error handling and user experience considerations.

Secure Actions #

Purpose: Execute simple actions that require authentication without returning values.

Preview:

context.secureAction(
  onSuccess: () => deleteAccount(),
  onFailure: (error) => showError(error),
  reason: 'Authenticate to delete account',
);

Use Cases

Use Case Example Best For
Account Deletion Delete user account Destructive operations
Settings Changes Update security settings Configuration changes
Data Export Export sensitive data Data operations
Payment Actions Process payment Financial transactions

Complete Example

class AccountSettings extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Delete Account Button
        ElevatedButton.icon(
          style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
          onPressed: () => _deleteAccount(context),
          icon: Icon(Icons.delete_forever),
          label: Text('Delete Account'),
        ),
        
        // Change Password Button
        ElevatedButton.icon(
          onPressed: () => _changePassword(context),
          icon: Icon(Icons.password),
          label: Text('Change Password'),
        ),
      ],
    );
  }

  void _deleteAccount(BuildContext context) {
    context.secureAction(
      reason: 'Please authenticate to delete your account permanently',
      onSuccess: () async {
        await ApiService.deleteAccount();
        Navigator.of(context).pushReplacementNamed('/welcome');
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Account deleted successfully')),
        );
      },
      onFailure: (error) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Failed to delete account: ${error.toString()}'),
            backgroundColor: Colors.red,
          ),
        );
      },
    );
  }

  void _changePassword(BuildContext context) {
    context.secureAction(
      reason: 'Authenticate to change your password',
      onSuccess: () {
        Navigator.of(context).pushNamed('/change-password');
      },
      onFailure: (error) => _showAuthError(context, error),
    );
  }
}

Async Actions #

Purpose: Execute asynchronous operations that require authentication without returning values.

Preview:

await context.guardoAsyncAction(
  onSuccess: () async => await uploadSecureData(),
  onFailure: (error) async => await logError(error),
  reason: 'Authenticate to upload data',
);

Use Cases

Use Case Example Best For
File Upload Upload encrypted files Long-running operations
Data Sync Sync sensitive data Background processes
API Calls Make secure API requests Network operations
Database Operations Update secure records Data persistence

Complete Example

class SecureFileManager extends StatefulWidget {
  @override
  _SecureFileManagerState createState() => _SecureFileManagerState();
}

class _SecureFileManagerState extends State<SecureFileManager> {
  bool _isUploading = false;
  double _uploadProgress = 0.0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (_isUploading)
          LinearProgressIndicator(value: _uploadProgress),
        
        ElevatedButton.icon(
          onPressed: _isUploading ? null : () => _uploadSecureFiles(context),
          icon: Icon(Icons.cloud_upload),
          label: Text('Upload Secure Files'),
        ),
        
        ElevatedButton.icon(
          onPressed: () => _syncData(context),
          icon: Icon(Icons.sync),
          label: Text('Sync Encrypted Data'),
        ),
      ],
    );
  }

  Future<void> _uploadSecureFiles(BuildContext context) async {
    setState(() => _isUploading = true);
    
    await context.guardoAsyncAction(
      reason: 'Authenticate to upload your secure files',
      onSuccess: () async {
        try {
          final files = await FileService.getSelectedFiles();
          
          for (int i = 0; i < files.length; i++) {
            await FileService.uploadEncryptedFile(files[i]);
            setState(() => _uploadProgress = (i + 1) / files.length);
          }
          
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('${files.length} files uploaded successfully'),
              backgroundColor: Colors.green,
            ),
          );
        } catch (e) {
          throw Exception('Upload failed: $e');
        }
      },
      onFailure: (error) async {
        await Logger.logError('File upload failed', error);
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Upload failed: ${error.toString()}'),
            backgroundColor: Colors.red,
          ),
        );
      },
    );
    
    setState(() {
      _isUploading = false;
      _uploadProgress = 0.0;
    });
  }

  Future<void> _syncData(BuildContext context) async {
    await context.guardoAsyncAction(
      reason: 'Authenticate to sync your encrypted data',
      onSuccess: () async {
        await DataService.syncEncryptedData();
        await CacheService.clearSensitiveCache();
        
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Data synced successfully')),
        );
      },
      onFailure: (error) async {
        await Logger.logError('Sync failed', error);
      },
    );
  }
}

Actions with Results #

Purpose: Execute operations that require authentication and return values.

Preview:

final result = await context.guardoActionWithResult<String>(
  onSuccess: () => getSecretKey(),
  onFailure: (error) => 'Default key',
  reason: 'Authenticate to access encryption key',
);

Use Cases

Use Case Example Best For
Key Retrieval Get encryption keys Security operations
Data Fetching Fetch sensitive data Data retrieval
Token Generation Generate auth tokens Authentication flows
Calculation Results Compute sensitive values Processing operations

Complete Example

class CryptoWallet extends StatefulWidget {
  @override
  _CryptoWalletState createState() => _CryptoWalletState();
}

class _CryptoWalletState extends State<CryptoWallet> {
  String? _balance;
  String? _privateKey;
  List<Transaction>? _transactions;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Balance Card
        Card(
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              children: [
                Text('Wallet Balance', style: TextStyle(fontSize: 16)),
                Text(
                  _balance ?? 'Hidden',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
                ElevatedButton(
                  onPressed: () => _getBalance(context),
                  child: Text('Show Balance'),
                ),
              ],
            ),
          ),
        ),
        
        // Actions
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton.icon(
              onPressed: () => _exportPrivateKey(context),
              icon: Icon(Icons.key),
              label: Text('Export Key'),
            ),
            ElevatedButton.icon(
              onPressed: () => _getTransactionHistory(context),
              icon: Icon(Icons.history),
              label: Text('History'),
            ),
          ],
        ),
        
        // Transaction List
        if (_transactions != null)
          Expanded(
            child: ListView.builder(
              itemCount: _transactions!.length,
              itemBuilder: (context, index) => TransactionTile(_transactions![index]),
            ),
          ),
      ],
    );
  }

  Future<void> _getBalance(BuildContext context) async {
    final balance = await context.guardoActionWithResult<String>(
      reason: 'Authenticate to view your wallet balance',
      onSuccess: () async {
        final wallet = await WalletService.getSecureWallet();
        return await wallet.getBalance();
      },
      onFailure: (error) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Failed to get balance: $error')),
        );
        return null;
      },
    );
    
    setState(() => _balance = balance);
  }

  Future<void> _exportPrivateKey(BuildContext context) async {
    final privateKey = await context.guardoAsyncActionWithResult<String>(
      reason: 'Authenticate to export your private key',
      onSuccess: () async {
        final wallet = await WalletService.getSecureWallet();
        return await wallet.exportPrivateKey();
      },
      onFailure: (error) async {
        await Logger.logSecurityEvent('Private key export failed', error);
        return null;
      },
    );
    
    if (privateKey != null) {
      _showPrivateKeyDialog(context, privateKey);
    }
  }

  Future<void> _getTransactionHistory(BuildContext context) async {
    final transactions = await context.guardoAsyncActionWithResult<List<Transaction>>(
      reason: 'Authenticate to view transaction history',
      onSuccess: () async {
        final wallet = await WalletService.getSecureWallet();
        return await wallet.getTransactionHistory();
      },
      onFailure: (error) async => <Transaction>[],
    );
    
    setState(() => _transactions = transactions);
  }
}

Device Capability Checks #

Purpose: Check device biometric capabilities and availability.

Preview:

final canAuth = await context.canAuthenticate();
final biometrics = await context.getAvailableBiometrics();

Use Cases

Use Case Example Best For
Feature Detection Show/hide biometric options UI adaptation
Fallback Planning Provide alternative auth User experience
Capability Display Show supported methods User information
Setup Validation Check before enabling Configuration

Complete Example

class BiometricSetup extends StatefulWidget {
  @override
  _BiometricSetupState createState() => _BiometricSetupState();
}

class _BiometricSetupState extends State<BiometricSetup> {
  bool? _canAuthenticate;
  bool? _isDeviceSupported;
  List<BiometricType>? _availableBiometrics;
  String _statusMessage = 'Checking device capabilities...';

  @override
  void initState() {
    super.initState();
    _checkCapabilities();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Status Card
        Card(
          color: _getStatusColor(),
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Column(
              children: [
                Icon(
                  _getStatusIcon(),
                  size: 48,
                  color: Colors.white,
                ),
                SizedBox(height: 8),
                Text(
                  _statusMessage,
                  style: TextStyle(color: Colors.white, fontSize: 16),
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ),
        ),
        
        // Capabilities List
        if (_availableBiometrics != null)
          Card(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Available Authentication Methods',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  ..._availableBiometrics!.map((biometric) => ListTile(
                    leading: Icon(_getBiometricIcon(biometric)),
                    title: Text(_getBiometricName(biometric)),
                    subtitle: Text(_getBiometricDescription(biometric)),
                    trailing: Icon(Icons.check, color: Colors.green),
                  )),
                ],
              ),
            ),
          ),
        
        // Setup Button
        if (_canAuthenticate == true)
          ElevatedButton.icon(
            onPressed: () => _enableBiometricSecurity(context),
            icon: Icon(Icons.security),
            label: Text('Enable Biometric Security'),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.green,
              padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
            ),
          ),
      ],
    );
  }

  Future<void> _checkCapabilities() async {
    try {
      final results = await Future.wait([
        context.canAuthenticate(),
        context.isDeviceSupported(),
        context.getAvailableBiometrics(),
      ]);
      
      setState(() {
        _canAuthenticate = results[0] as bool;
        _isDeviceSupported = results[1] as bool;
        _availableBiometrics = results[2] as List<BiometricType>;
        _updateStatusMessage();
      });
    } catch (e) {
      setState(() {
        _canAuthenticate = false;
        _isDeviceSupported = false;
        _statusMessage = 'Error checking capabilities: $e';
      });
    }
  }

  void _updateStatusMessage() {
    if (_canAuthenticate == true && _isDeviceSupported == true) {
      _statusMessage = 'Device supports biometric authentication';
    } else if (_isDeviceSupported == false) {
      _statusMessage = 'Device does not support biometric authentication';
    } else if (_canAuthenticate == false) {
      _statusMessage = 'Biometric authentication not available. Please set up biometrics in device settings.';
    }
  }

  Color _getStatusColor() {
    if (_canAuthenticate == true) return Colors.green;
    if (_canAuthenticate == false) return Colors.orange;
    return Colors.grey;
  }

  IconData _getStatusIcon() {
    if (_canAuthenticate == true) return Icons.verified_user;
    if (_canAuthenticate == false) return Icons.warning;
    return Icons.help_outline;
  }

  String _getBiometricName(BiometricType type) {
    switch (type) {
      case BiometricType.face: return 'Face Recognition';
      case BiometricType.fingerprint: return 'Fingerprint';
      case BiometricType.iris: return 'Iris Scanner';
      case BiometricType.weak: return 'Device PIN/Pattern';
      case BiometricType.strong: return 'Strong Biometrics';
    }
  }

  IconData _getBiometricIcon(BiometricType type) {
    switch (type) {
      case BiometricType.face: return Icons.face;
      case BiometricType.fingerprint: return Icons.fingerprint;
      case BiometricType.iris: return Icons.visibility;
      case BiometricType.weak: return Icons.pin;
      case BiometricType.strong: return Icons.security;
    }
  }

  String _getBiometricDescription(BiometricType type) {
    switch (type) {
      case BiometricType.face: return 'Use your face to unlock';
      case BiometricType.fingerprint: return 'Use your fingerprint to unlock';
      case BiometricType.iris: return 'Use your iris to unlock';
      case BiometricType.weak: return 'Use device PIN or pattern';
      case BiometricType.strong: return 'High-security biometric method';
    }
  }

  void _enableBiometricSecurity(BuildContext context) {
    // Navigate to app settings or show configuration dialog
    Navigator.of(context).pushNamed('/security-settings');
  }
}

App Control Actions #

Purpose: Control app lock state and authentication flow.

Preview:

context.lockApp();                    // Lock immediately
final success = await context.unlockApp(); // Unlock with auth
context.resetLockTimer();             // Reset auto-lock timer

Use Cases

Use Case Example Best For
Manual Lock Lock when leaving app Security control
Emergency Lock Lock on security threat Security response
Session Extension Reset timer on activity User experience
Programmatic Unlock Unlock after verification Automation

Complete Example

class SecurityControlPanel extends StatefulWidget {
  @override
  _SecurityControlPanelState createState() => _SecurityControlPanelState();
}

class _SecurityControlPanelState extends State<SecurityControlPanel> {
  Timer? _activityTimer;
  int _activityCount = 0;

  @override
  void initState() {
    super.initState();
    _startActivityMonitoring();
  }

  @override
  void dispose() {
    _activityTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Status Dashboard
        _buildStatusDashboard(context),
        
        // Control Buttons
        _buildControlButtons(context),
        
        // Security Actions
        _buildSecurityActions(context),
        
        // Activity Monitor
        _buildActivityMonitor(context),
      ],
    );
  }

  Widget _buildStatusDashboard(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text(
              'Security Status',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                _buildStatusItem(
                  icon: context.isAuthenticated ? Icons.lock_open : Icons.lock,
                  label: context.isAuthenticated ? 'Unlocked' : 'Locked',
                  color: context.isAuthenticated ? Colors.green : Colors.red,
                ),
                _buildStatusItem(
                  icon: Icons.timer,
                  label: 'Auto-lock: 5min',
                  color: Colors.blue,
                ),
                _buildStatusItem(
                  icon: Icons.activity,
                  label: 'Activity: $_activityCount',
                  color: Colors.orange,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusItem({
    required IconData icon,
    required String label,
    required Color color,
  }) {
    return Column(
      children: [
        Icon(icon, color: color, size: 32),
        SizedBox(height: 4),
        Text(
          label,
          style: TextStyle(color: color, fontWeight: FontWeight.bold),
        ),
      ],
    );
  }

  Widget _buildControlButtons(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'App Controls',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton.icon(
                  onPressed: () => _lockApp(context),
                  icon: Icon(Icons.lock),
                  label: Text('Lock Now'),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                ),
                ElevatedButton.icon(
                  onPressed: () => _unlockApp(context),
                  icon: Icon(Icons.lock_open),
                  label: Text('Unlock'),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
                ),
                ElevatedButton.icon(
                  onPressed: () => _resetTimer(context),
                  icon: Icon(Icons.timer_off),
                  label: Text('Reset Timer'),
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSecurityActions(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Security Actions',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 16),
            ListTile(
              leading: Icon(Icons.logout, color: Colors.orange),
              title: Text('Secure Logout'),
              subtitle: Text('Lock app and clear session'),
              onTap: () => _secureLogout(context),
            ),
            ListTile(
              leading: Icon(Icons.emergency, color: Colors.red),
              title: Text('Emergency Lock'),
              subtitle: Text('Immediate lock with notification clear'),
              onTap: () => _emergencyLock(context),
            ),
            ListTile(
              leading: Icon(Icons.schedule, color: Colors.blue),
              title: Text('Scheduled Lock'),
              subtitle: Text('Set custom lock timer'),
              onTap: () => _showScheduleDialog(context),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildActivityMonitor(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Activity Monitor',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8),
            Text('User activity count: $_activityCount'),
            Text('Last activity: ${DateTime.now().toString().substring(0, 19)}'),
            SizedBox(height: 8),
            LinearProgressIndicator(
              value: (_activityCount % 100) / 100,
              backgroundColor: Colors.grey[300],
              valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
            ),
          ],
        ),
      ),
    );
  }

  void _lockApp(BuildContext context) {
    try {
      context.lockApp();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('App locked successfully'),
          backgroundColor: Colors.red,
          duration: Duration(seconds: 1),
        ),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Failed to lock app: $e'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }

  Future<void> _unlockApp(BuildContext context) async {
    try {
      final success = await context.unlockApp();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(success ? 'App unlocked successfully' : 'Failed to unlock app'),
          backgroundColor: success ? Colors.green : Colors.red,
        ),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Unlock error: $e'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }

  void _resetTimer(BuildContext context) {
    context.resetLockTimer();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Auto-lock timer reset'),
        backgroundColor: Colors.blue,
      ),
    );
  }

  void _secureLogout(BuildContext context) {
    context.lockApp();
    // Clear user session, cache, sensitive data
    UserSession.clear();
    CacheService.clearSensitive();
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Secure logout completed'),
        backgroundColor: Colors.orange,
      ),
    );
  }

  void _emergencyLock(BuildContext context) {
    context.lockApp();
    // Clear notifications, hide sensitive content
    NotificationService.clearAll();
    AppStateService.hideFromRecents();
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Emergency lock activated'),
        backgroundColor: Colors.red,
      ),
    );
  }

  void _showScheduleDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Schedule Auto-Lock'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              title: Text('1 minute'),
              onTap: () => _setCustomTimer(Duration(minutes: 1)),
            ),
            ListTile(
              title: Text('5 minutes'),
              onTap: () => _setCustomTimer(Duration(minutes: 5)),
            ),
            ListTile(
              title: Text('15 minutes'),
              onTap: () => _setCustomTimer(Duration(minutes: 15)),
            ),
            ListTile(
              title: Text('Disable'),
              onTap: () => _setCustomTimer(null),
            ),
          ],
        ),
      ),
    );
  }

  void _setCustomTimer(Duration? duration) {
    Navigator.pop(context);
    // This would require updating the GuardoConfig
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          duration != null 
            ? 'Auto-lock set to ${duration.inMinutes} minutes'
            : 'Auto-lock disabled'
        ),
      ),
    );
  }

  void _startActivityMonitoring() {
    _activityTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() => _activityCount++);
      
      // Reset lock timer on activity
      if (_activityCount % 30 == 0) {
        context.resetLockTimer();
      }
    });
  }
}

Examples #

Basic Authentication #

import 'package:flutter/material.dart';
import 'package:guardo/guardo.dart';

class BasicAuthApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Guardo(
      config: GuardoConfig(
        localizedReason: 'Authenticate to access your secure data',
        lockTimeout: Duration(minutes: 2),
      ),
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

Custom Lock Screen #

Guardo(
  lockScreen: (context, onUnlock) => CustomLockScreen(onUnlock: onUnlock),
  child: YourApp(),
)

class CustomLockScreen extends StatelessWidget {
  final VoidCallback onUnlock;
  
  const CustomLockScreen({required this.onUnlock});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black87,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.security,
              size: 120,
              color: Colors.white,
              semanticLabel: 'Security lock icon',
            ),
            SizedBox(height: 40),
            Text(
              'Secure Area',
              style: TextStyle(
                color: Colors.white,
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 20),
            Text(
              'Please authenticate to continue',
              style: TextStyle(color: Colors.white70),
            ),
            SizedBox(height: 40),
            ElevatedButton.icon(
              onPressed: onUnlock,
              icon: Icon(Icons.fingerprint),
              label: Text('Authenticate'),
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Manual Lock/Unlock #

class ControlPanel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Status indicator
        Card(
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Row(
              children: [
                Icon(
                  context.isAuthenticated ? Icons.lock_open : Icons.lock,
                  color: context.isAuthenticated ? Colors.green : Colors.red,
                ),
                SizedBox(width: 8),
                Text(
                  context.isAuthenticated ? 'Unlocked' : 'Locked',
                  style: TextStyle(
                    color: context.isAuthenticated ? Colors.green : Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ),
        ),
        
        // Control buttons
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: () => context.lockApp(),
              child: Text('Lock App'),
              style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
            ),
            ElevatedButton(
              onPressed: () async {
                final success = await context.unlockApp();
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text(success ? 'Unlocked!' : 'Failed to unlock'),
                    backgroundColor: success ? Colors.green : Colors.red,
                  ),
                );
              },
              child: Text('Unlock App'),
              style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
            ),
          ],
        ),
      ],
    );
  }
}

Advanced Error Handling #

class SecureAction extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => _performSecureAction(context),
      child: Text('Delete Account'),
    );
  }

  Future<void> _performSecureAction(BuildContext context) async {
    try {
      await context.secureAction(
        reason: 'Please authenticate to delete your account',
        onSuccess: () async {
          // Perform the actual deletion
          await deleteAccount();
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Account deleted successfully')),
          );
        },
      );
    } on BiometricLockoutException catch (e) {
      _showLockoutDialog(context, e);
    } on BiometricUnavailableException catch (e) {
      _showUnavailableDialog(context, e);
    } on AuthenticationFailedException catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Authentication failed: ${e.message}'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }

  void _showLockoutDialog(BuildContext context, BiometricLockoutException e) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Biometrics Locked'),
        content: Text(e.message),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('OK'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              // Offer device credentials as fallback
              _tryDeviceCredentials(context);
            },
            child: Text('Use PIN/Password'),
          ),
        ],
      ),
    );
  }

  void _showUnavailableDialog(BuildContext context, BiometricUnavailableException e) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Biometrics Unavailable'),
        content: Text('${e.message}\n\nPlease ensure biometric authentication is set up on your device.'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('OK'),
          ),
        ],
      ),
    );
  }

  Future<void> _tryDeviceCredentials(BuildContext context) async {
    // Implementation for device credential authentication
  }
}

Smart Authentication Detection & Control #

New in v0.1.0! Handle devices without authentication methods gracefully:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:guardo/guardo.dart';

class SmartAuthApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Guardo(
      // Disable authentication in debug mode for faster development
      enabled: !kDebugMode,
      
      // Optional: Check authentication availability before showing UI
      child: FutureBuilder<bool>(
        future: _checkAuthMethods(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          }
          
          return MaterialApp(
            home: AuthAwareHomePage(hasAuth: snapshot.data ?? false),
          );
        },
      ),
    );
  }

  Future<bool> _checkAuthMethods() async {
    // This will be called within Guardo's context
    try {
      final service = GuardoService();
      return await service.hasAuthenticationMethods;
    } catch (e) {
      return false;
    }
  }
}

class AuthAwareHomePage extends StatelessWidget {
  final bool hasAuth;
  
  const AuthAwareHomePage({required this.hasAuth});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Smart Auth App'),
        actions: [
          // Show auth status
          if (context.isGuardoEnabled())
            Icon(Icons.security, color: Colors.green)
          else
            Icon(Icons.security_update_warning, color: Colors.orange),
        ],
      ),
      body: Column(
        children: [
          // Auth status card
          Card(
            margin: EdgeInsets.all(16),
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Authentication Status',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text('Guardo Enabled: ${context.isGuardoEnabled()}'),
                  Text('Has Auth Methods: $hasAuth'),
                  Text('Is Authenticated: ${context.isAuthenticated}'),
                ],
              ),
            ),
          ),
          
          // Smart lock button
          ElevatedButton.icon(
            onPressed: () => _smartLockApp(context),
            icon: Icon(Icons.lock),
            label: Text('Smart Lock'),
          ),
        ],
      ),
    );
  }

  Future<void> _smartLockApp(BuildContext context) async {
    try {
      // Check if authentication is available before attempting to lock
      if (await context.hasAuthenticationMethods()) {
        await context.lockApp();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('App locked successfully')),
        );
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Cannot lock: No authentication methods available'),
            backgroundColor: Colors.orange,
          ),
        );
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Lock failed: $e'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }
}

Security Considerations #

Best Practices #

  1. Sensitive Data Handling

    // Good: Use secure actions for sensitive operations
    context.secureAction(
      onSuccess: () => transferMoney(amount),
      reason: 'Authenticate to transfer money',
    );
    
  2. Timeout Configuration

    // Good: Set appropriate timeouts
    GuardoConfig(
      lockTimeout: Duration(minutes: 5), // Adjust based on app sensitivity
    )
    
  3. Error Information

    // Good: Don't expose sensitive error details to users
    onFailure: (error) {
      logger.error('Auth failed: $error'); // Log detailed error
      showUserFriendlyMessage(); // Show generic message to user
    }
    

Security Features #

  • Automatic Lockout Handling - Graceful degradation when biometrics fail
  • Session Management - Proper cleanup of authentication sessions
  • State Protection - Secure state transitions and validation
  • Platform Integration - Uses platform-native security features

Troubleshooting #

Common Issues #

1. Biometrics Not Working

// Check device capabilities first
final canAuth = await context.canAuthenticate();
final isSupported = await context.isDeviceSupported();

if (!canAuth || !isSupported) {
  // Handle unsupported device
  showFallbackOptions();
}

2. App Not Locking

// Ensure Guardo widget wraps your entire app
Guardo(
  config: GuardoConfig(
    lockTimeout: Duration(minutes: 5), // Make sure timeout is set
  ),
  child: MaterialApp(...), // App should be inside Guardo
)

3. Custom Lock Screen Not Showing

// Make sure to return a complete widget
lockScreen: (context, onUnlock) {
  return Scaffold( // Return a complete screen
    body: YourLockScreenContent(onUnlock: onUnlock),
  );
}

4. Authentication State Issues

// Use the context extensions within Guardo widget tree
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // This works - within Guardo tree
    final isAuth = context.isAuthenticated;
    
    return YourWidget();
  }
}

5. lockApp() Throws UnsupportedError

// Handle cases where authentication is disabled or unavailable
try {
  await context.lockApp();
} on UnsupportedError catch (e) {
  // Authentication is disabled or no methods available
  print('Cannot lock: $e');
  // Provide alternative action or information to user
}

6. App Not Showing on Devices Without Biometrics

// Check if your app handles the bypass scenario correctly
Guardo(
  // Make sure enabled is not accidentally set to false
  enabled: true,
  
  // Guardo will automatically bypass authentication if no methods available
  child: YourApp(), // This should show directly on devices without auth
)

Debug Mode #

Enable detailed logging:

import 'package:flutter/foundation.dart';

// In debug mode, Guardo automatically provides detailed logs
if (kDebugMode) {
  // Check console for authentication flow logs
}

Contributing #

We welcome contributions! Please see our Contributing Guidelines for details.

Development Setup #

# Clone the repository
git clone https://github.com/fathialamre/guardo.git

# Install dependencies
flutter pub get

# Run tests
flutter test

# Run example app
cd example
flutter run

Reporting Issues #

When reporting issues, please include:

  • Flutter version (flutter --version)
  • Platform (iOS/Android) and version
  • Device model and biometric capabilities
  • Complete error messages and stack traces
  • Minimal reproduction code

License #

This project is licensed under the MIT License - see the LICENSE file for details.


Acknowledgments #

  • Built with local_auth package
  • Inspired by the Flutter community's security needs
  • Thanks to all contributors and users

Made with love for the Flutter community

Star us on GitHubReport BugRequest Feature

9
likes
150
points
29
downloads

Publisher

verified publisherqomrah.ly

Weekly Downloads

A Flutter package that provides secure app entry point with auto-locking, biometric authentication, and lifecycle management.

Repository (GitHub)
View/report issues

Topics

#security #authentication #biometric #app-lock #privacy

Documentation

API reference

License

MIT (license)

Dependencies

flutter, local_auth

More

Packages that depend on guardo