biometric_auth_lock 0.2.1 copy "biometric_auth_lock: ^0.2.1" to clipboard
biometric_auth_lock: ^0.2.1 copied to clipboard

A reusable biometric authentication and PIN lock service for Flutter apps with support for fingerprint, Face ID, and PIN fallback

Biometric Auth Lock #

πŸ“– Documentation en franΓ§ais

A reusable biometric authentication and PIN lock service for Flutter applications with support for fingerprint, Face ID, and PIN fallback.

Features #

  • Biometric Authentication: Support for fingerprint and Face ID
  • PIN Fallback: Optional PIN code authentication when biometrics aren't available
  • Multi-Platform: Works on Android and iOS
  • Persistent Storage: Save authentication state across app restarts
  • Flexible API: High-level and low-level APIs for different use cases
  • BiometricLockService: App-level facade for one-line integration with dependency injection
  • Type-Safe: Interface-based design with functional error handling (Either)
  • Comprehensive Exception Handling: Specific exceptions for different failure scenarios

Installation #

Add to your pubspec.yaml:

dependencies:
  biometric_auth_lock:
    path: packages/biometric_auth_lock

Platform Setup #

Android #

1. Permissions β€” Add the following to your AndroidManifest.xml:

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

2. MainActivity β€” Your MainActivity must extend FlutterFragmentActivity (instead of FlutterActivity). The local_auth plugin requires FlutterFragmentActivity because the Android biometric prompt APIs rely on fragment management.

If you don't make this change you'll see:

PlatformException(no_fragment_activity, local_auth plugin requires activity
to be a FragmentActivity., null, null)

Kotlin (android/app/src/main/kotlin/.../MainActivity.kt):

package com.example.your_app_name // Keep your original package name

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity: FlutterFragmentActivity() {
    // You can leave this body empty
}

Java (android/app/src/main/java/.../MainActivity.java):

package com.example.your_app_name; // Keep your original package name

import io.flutter.embedding.android.FlutterFragmentActivity;

public class MainActivity extends FlutterFragmentActivity {
    // You can leave this body empty
}

iOS #

Add the following to your Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>Why is my app authenticating using face id?</string>

Usage #

BiometricLockService is the simplest way to integrate biometric auth. It wraps the entire package behind a single facade designed for dependency injection:

import 'package:biometric_auth_lock/biometric_auth_lock.dart';

// Register once in your service locator
final lockService = await BiometricLockService.withDefaults();

// --- Check device capabilities ---
final supported = await lockService.isSupported;   // Sensor present & enrolled
final faceIdOk  = await lockService.isFaceIdAvailable;
final fingerOk  = await lockService.isFingerprintAvailable;
final pinOk     = await lockService.isPinConfigured;

// --- Enable the lock (prompts user once) ---
final enabled = await lockService.enable();

// --- Gate a critical operation ---
final result = await lockService.unlock(
  reason: 'Authenticatez-vous pour effectuer le paiement',
);
if (result.unlocked) {
  // proceed with sensitive operation
}

// --- Or use guard to wrap any action ---
final paymentResult = await lockService.guard(
  () => performPayment(),
  reason: 'Authentifiez-vous pour confirmer le paiement',
);

// --- PIN fallback ---
await lockService.setPin('1234');
final pinOk = await lockService.unlockWithPin('1234');

// --- Disable the lock ---
await lockService.disable();

BiometricUnlockResult

The unlock() method returns a BiometricUnlockResult enum:

Value Meaning
success User authenticated successfully
failed Authentication was cancelled or failed
notConfigured Biometric lock hasn't been enabled yet
notSupported Device doesn't support biometrics

Use the .unlocked extension getter as a quick success check.

High-Level API (LocalAuthService) #

For advanced control, LocalAuthService provides the underlying implementation with automatic setup and fallback handling.

import 'package:biometric_auth_lock/biometric_auth_lock.dart';
import 'package:local_storage_impl/local_storage_impl.dart';

// Initialize services
final localStorage = await SharedPreferencesLocalStorage.getInstance();
final biometricAuthStorage = LocalStorageBiometricAuthService(localStorage);
final biometricIdentity = LocalAuthBiometricIdentityService();
final localAuthService = LocalAuthService(biometricAuthStorage, biometricIdentity);

// Check status
final status = await localAuthService.status();
print('Biometric configured: ${status.isBiometricAuthConfigured}');
print('PIN configured: ${status.isPinConfigured}');
print('Device supported: ${status.isBiometricAuthSupported}');

// Authenticate with biometrics
final result = await localAuthService.resolveWithBiometric(
  'Please authenticate to continue',
  signInTitle: 'Sign In',
  cancelButtonMessage: 'Cancel',
  fallbackToPin: true,
);

if (result.authenticated) {
  print('Authentication successful!');
} else if (!result.isSupported) {
  print('Biometrics not supported on this device');
} else if (!result.isConfigured) {
  print('Biometrics not configured');
}

Setting Up Authentication #

// Setup biometric authentication
final setupResult = await localAuthService.setupBiometric(
  'Setup biometric authentication',
  signInTitle: 'Setup',
  cancelButtonMessage: 'Skip',
);

if (setupResult.ok) {
  print('Biometric authentication configured!');
} else if (!setupResult.isAvailable) {
  print('Biometrics not available, setting up PIN...');
  await localAuthService.setupPin('1234');
}

PIN Code Authentication #

// Setup PIN
await localAuthService.setupPin('1234');

// Authenticate with PIN
final pinResult = await localAuthService.resolveWithPin('1234');

if (pinResult.authenticated) {
  print('PIN authentication successful!');
} else if (!pinResult.isConfigured) {
  print('PIN not configured');
} else {
  print('Incorrect PIN');
}

Declining Authentication Methods #

// User declines biometric auth
await localAuthService.declineBioMetricAuth();

// User declines PIN auth
await localAuthService.declinePinCodeAuth();

Low-Level API (Advanced) #

For more control, use the individual services directly.

Biometric Identity Service

final biometricService = LocalAuthBiometricIdentityService();

// Check device status
final status = await biometricService.status();
if (status.canAuthenticateWithBiometrics && status.isDeviceSupported) {
  // Authenticate
  final result = await biometricService.authenticate(
    'Authenticate to access app',
    signInTitle: 'Sign In',
    cancelButtonMessage: 'Cancel',
  );

  result.fold(
    ifLeft: (exception) {
      if (exception is BiometricNotSupportedException) {
        print('Biometrics not supported');
      } else if (exception is BiometricAuthLockoutException) {
        print('Too many failed attempts');
      } else if (exception is BiometricAuthFailedException) {
        print('Authentication failed');
      }
    },
    ifRight: (authResult) {
      print('Authenticated at: ${authResult.timestamp}');
    },
  );
}

// Check available biometric types
final types = await biometricService.getAvailableTypes();
for (final type in types) {
  print('Available: $type');
}

Biometric Auth Storage Service

final localStorage = await SharedPreferencesLocalStorage.getInstance();
final authStorage = LocalStorageBiometricAuthService(localStorage);

// Save authentication state
await authStorage.setData(
  BiometricAuthData(
    isBiometricAuthConfigured: true,
    hasDeclinedBioMetricAuth: false,
    hasDeclinedPinCodeAuth: false,
    isBiometricAuthSupported: true,
    pinCodeFallback: '1234',
    lastAuthenticated: DateTime.now(),
  ),
);

// Retrieve authentication state
final data = await authStorage.getData();
print('Biometric configured: ${data.isBiometricAuthConfigured}');
print('PIN configured: ${data.isPinConfigured}');

// Clear data
await authStorage.clearData();

API Reference #

App-level facade that centralizes FaceID / fingerprint lock management and gating of critical operations (payments, transfers, PIN change, etc.). Designed for dependency injection.

Factory:

  • BiometricLockService.withDefaults() β€” wires all default implementations backed by persistent shared-preferences storage. Ideal for quick integration.
  • BiometricLockService.withStorage(LocalStorage localStorage) β€” same as above but with a custom storage backend. Use this from a service locator when your app already manages a LocalStorage instance.

Storage backends:

local_storage_impl ships with three LocalStorage implementations. Choose based on your security requirements:

Backend When to use
SharedPreferencesLocalStorage Default; plain-text persistence. Used by withDefaults().
SecureLocalStorage Production apps storing PIN codes. Backed by flutter_secure_storage (Android Keystore / iOS Keychain).
MapLocalStorage Unit tests or prototyping (in-memory, no persistence).
import 'package:local_storage_impl/local_storage_impl.dart';

// --- Secure storage (recommended for production) ---
final lockService = BiometricLockService.withStorage(
  await SecureLocalStorage.getInstance(),
);

// --- SharedPreferences (what withDefaults() does internally) ---
final lockService = BiometricLockService.withStorage(
  await SharedPreferencesLocalStorage.getInstance(),
);

// --- In-memory (tests only) ---
final lockService = BiometricLockService.withStorage(MapLocalStorage());

Methods:

  • Future<BiometricAuthData> status() β€” raw persisted state
  • Future<bool> get isEnabled β€” lock is configured and active
  • Future<bool> get isSupported β€” device supports biometrics (sensor + enrolled)
  • Future<bool> get isFaceIdAvailable β€” FaceID is available on this device
  • Future<bool> get isFingerprintAvailable β€” fingerprint is available on this device
  • Future<bool> get isPinConfigured β€” a fallback PIN has been configured
  • Future<bool> enable(...) β€” prompts the user to enable the biometric lock
  • Future<void> disable() β€” marks biometric auth as declined by the user
  • Future<BiometricUnlockResult> unlock(...) β€” prompts before a critical operation
  • Future<T?> guard<T>(action, ...) β€” runs action only if biometric check passes
  • Future<bool> unlockWithPin(String pinCode) β€” verifies the fallback PIN
  • Future<bool> setPin(String pinCode) β€” stores or replaces the fallback PIN
  • Future<void> declinePin() β€” marks PIN fallback as declined

BiometricUnlockResult

Value Meaning
success User successfully authenticated
failed Authentication failed or was cancelled
notConfigured Biometric lock isn't configured yet
notSupported Device doesn't support biometrics

ILocalAuthService #

High-level authentication service interface.

Methods:

  • Future<BiometricAuthData> status() - Get current authentication status
  • Future<({bool authenticated, bool isConfigured, bool isSupported})> resolveWithBiometric(...) - Authenticate with biometrics
  • Future<({bool ok, bool isAvailable})> setupBiometric(...) - Setup biometric authentication
  • Future<({bool authenticated, bool isConfigured})> resolveWithPin(String pinCode) - Authenticate with PIN
  • Future<bool> setupPin(String pinCode) - Setup PIN code
  • Future<void> declineBioMetricAuth() - Mark biometric auth as declined
  • Future<void> declinePinCodeAuth() - Mark PIN auth as declined

IBiometricIdentityService #

Low-level biometric platform interface.

Methods:

  • Future<Either<BiometricAuthException, BiometricAuthResult>> authenticate(...) - Perform biometric authentication
  • Future<List<IBiometricIdentityType>> getAvailableTypes() - Get available biometric types
  • Future<bool> isTypeAvailable(IBiometricIdentityType type) - Check if specific type is available
  • Future<BiometricIdentityTypeStatus> status() - Get device capabilities

IBiometricAuthService #

Authentication state storage interface.

Methods:

  • Future<BiometricAuthData> getData() - Get stored auth data
  • Future<void> setData(BiometricAuthData data) - Save auth data
  • Future<void> clearData() - Clear stored data

BiometricAuthData #

Authentication state model.

Properties:

  • bool isBiometricAuthConfigured - Is biometric auth configured
  • bool isPinConfigured - Is PIN configured (computed)
  • bool isBiometricAuthSupported - Does device support biometrics
  • bool hasDeclinedBioMetricAuth - User declined biometric setup
  • bool hasDeclinedPinCodeAuth - User declined PIN setup
  • String? pinCodeFallback - Stored PIN code
  • DateTime? lastAuthenticated - Last authentication timestamp

Exceptions #

  • BiometricAuthException - Base exception class
  • BiometricNotSupportedException - Biometrics not supported on device
  • BiometricAuthFailedException - Authentication failed
  • BiometricTypeNotAvailableException - Requested biometric type unavailable
  • BiometricAuthLockoutException - Too many failed attempts

Complete Example #

import 'package:biometric_auth_lock/biometric_auth_lock.dart';

class AuthManager {
  late final BiometricLockService _lockService;

  Future<void> initialize() async {
    _lockService = await BiometricLockService.withDefaults();
  }

  /// Authenticate before a critical operation (e.g., payment).
  Future<bool> authenticate() async {
    // Guard wraps the action β€” runs only if biometric check passes
    final result = await _lockService.guard(
      () async => true, // your critical operation here
      reason: 'Authentifiez-vous pour continuer',
    );

    return result ?? false;
  }

  /// Setup biometrics or fall back to PIN.
  Future<bool> setupAuthentication() async {
    // Try biometric first
    final enabled = await _lockService.enable();
    if (enabled) return true;

    // Fall back to PIN if biometrics aren't available
    if (!await _lockService.isSupported) {
      final pinCode = await showPinSetupDialog(); // Your UI
      return await _lockService.setPin(pinCode);
    }

    return false;
  }

  Future<String> showPinSetupDialog() async {
    // Your PIN setup UI
    return '1234';
  }
}

With LocalAuthService (Advanced) #

import 'package:biometric_auth_lock/biometric_auth_lock.dart';
import 'package:local_storage_impl/local_storage_impl.dart';

class AuthManager {
  late final ILocalAuthService _authService;

  Future<void> initialize() async {
    final localStorage = await SharedPreferencesLocalStorage.getInstance();
    final authStorage = LocalStorageBiometricAuthService(localStorage);
    final biometricService = LocalAuthBiometricIdentityService();
    _authService = LocalAuthService(authStorage, biometricService);
  }

  Future<bool> authenticate() async {
    // Check if already configured
    final status = await _authService.status();

    if (status.isBiometricAuthConfigured) {
      // Use biometrics
      final result = await _authService.resolveWithBiometric(
        'Authenticate to continue',
        signInTitle: 'Sign In',
        cancelButtonMessage: 'Cancel',
        fallbackToPin: true,
      );
      return result.authenticated;
    } else if (status.isPinConfigured) {
      // Fall back to PIN (would show PIN input UI)
      final pinCode = await showPinInputDialog(); // Your UI
      final result = await _authService.resolveWithPin(pinCode);
      return result.authenticated;
    } else {
      // Not configured, setup first
      return await setupAuthentication();
    }
  }

  Future<bool> setupAuthentication() async {
    // Try biometric first
    final bioResult = await _authService.setupBiometric(
      'Setup biometric authentication',
      signInTitle: 'Setup',
      cancelButtonMessage: 'Skip',
    );

    if (bioResult.ok) {
      return true;
    }

    // Fall back to PIN
    if (!bioResult.isAvailable) {
      final pinCode = await showPinSetupDialog(); // Your UI
      return await _authService.setupPin(pinCode);
    }

    return false;
  }

  Future<String> showPinInputDialog() async {
    // Your PIN input UI
    return '1234';
  }

  Future<String> showPinSetupDialog() async {
    // Your PIN setup UI
    return '1234';
  }
}

Testing #

The interface-based design makes testing easy:

class MockBiometricAuthService implements IBiometricAuthService {
  BiometricAuthData? _data;

  @override
  Future<BiometricAuthData> getData() async =>
      _data ?? BiometricAuthData(
        isBiometricAuthConfigured: false,
        hasDeclinedBioMetricAuth: false,
        hasDeclinedPinCodeAuth: false,
        isBiometricAuthSupported: true,
      );

  @override
  Future<void> setData(BiometricAuthData data) async {
    _data = data;
  }

  @override
  Future<void> clearData() async {
    _data = null;
  }
}

Dependencies #

  • dart3z - Functional programming (Either)
  • flutter - Flutter SDK
  • json_annotation: ^4.9.0 - JSON serialization
  • local_auth: ^2.3.0 - Platform biometric auth
  • local_auth_android: ^1.0.52 - Android implementation
  • local_auth_darwin: ^1.6.0 - iOS/macOS implementation
  • local_storage_impl: ^0.0.5 - Persistent storage

License #

MIT License - See LICENSE file for details.

2
likes
90
points
141
downloads

Documentation

API reference

Publisher

verified publishertheresilient.dev

Weekly Downloads

A reusable biometric authentication and PIN lock service for Flutter apps with support for fingerprint, Face ID, and PIN fallback

Repository (GitHub)
View/report issues

Topics

#biometric #authentication #lock #local #pin

License

MIT (license)

Dependencies

dart3z, flutter, json_annotation, local_auth, local_auth_android, local_auth_darwin, local_storage_impl

More

Packages that depend on biometric_auth_lock