DEV Community

Cover image for How to Implement Biometric Authentication in a Flutter App (The Right Way)
Codexlancers
Codexlancers

Posted on

How to Implement Biometric Authentication in a Flutter App (The Right Way)

In today's world, security is no longer optional - it's expected. Whether it's a fintech app, a fitness tracker, or an internal company tool, users want fast and secure access without the hassle of remembering passwords.

That's exactly where biometric authentication comes in.

In this guide, we'll walk through how we implement biometric authentication in a Flutter app, the practical approach we follow in production, and the common mistakes developers often make (and how to avoid them).

Why Biometric Authentication?

Before jumping into implementation, let's quickly understand why it matters:

  • Faster login experience (no typing passwords)
  • More secure than traditional authentication
  • Native support across Android & iOS
  • Better user trust and retention

What We Use in Flutter

To implement biometric authentication, we rely on:

  • local_auth package (official Flutter plugin)
  • Native biometric APIs under the hood (Face ID, Touch ID, Fingerprint)

Step 1: Add Dependency

dependencies:
  local_auth: ^3.0.1
Enter fullscreen mode Exit fullscreen mode

Then run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Step 2: Platform Setup

✅ Android Setup

Inside android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
Enter fullscreen mode Exit fullscreen mode

Also ensure:

<uses-feature android:name="android.hardware.fingerprint" android:required="false"/>
Enter fullscreen mode Exit fullscreen mode

✅ iOS Setup

Inside ios/Runner/Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to authenticate you securely</string>
Enter fullscreen mode Exit fullscreen mode

⚠️ Without this, Face ID will NOT work and your app may crash.

Step 3: Implement Biometric Logic

Here's how we structure it in production:

import 'package:flutter/foundation.dart';
import 'package:local_auth/local_auth.dart';

class BiometricService {
  final LocalAuthentication _auth = LocalAuthentication();

  /// Check if device supports biometrics
  Future<bool> isBiometricAvailable() async {
    try {
      final bool canCheckBiometrics = await _auth.canCheckBiometrics;
      final bool isDeviceSupported = await _auth.isDeviceSupported();

      return canCheckBiometrics && isDeviceSupported;
    } catch (e) {
      debugPrint('Biometric availability error: $e');
      return false;
    }
  }

  /// Get available biometric types (fingerprint, face, etc.)
  Future<List<BiometricType>> getAvailableBiometrics() async {
    try {
      return await _auth.getAvailableBiometrics();
    } catch (e) {
      debugPrint('Error fetching biometrics: $e');
      return [];
    }
  }

  /// Authenticate user (with optional fallback to device PIN/password)
  Future<bool> authenticate({
    bool biometricOnly = false,
  }) async {
    try {
      final bool isAvailable = await isBiometricAvailable();

      if (!isAvailable) {
        debugPrint('Biometric not available on this device');
        return false;
      }

      final bool isAuthenticated = await _auth.authenticate(
        localizedReason: 'Please authenticate to continue',
        options: AuthenticationOptions(
          biometricOnly: biometricOnly,
          stickyAuth: true,
          useErrorDialogs: true,
        ),
      );

      return isAuthenticated;
    } catch (e) {
      debugPrint('Authentication error: $e');
      return false;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Use It in UI

final BiometricService biometricService = BiometricService();

Future<void> loginWithBiometrics(BuildContext context) async {
  final bool isAuthenticated = await biometricService.authenticate();

  if (isAuthenticated) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Authentication successful'),
      ),
    );
  } else {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Authentication failed. Please try again'),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Works for Face ID & Fingerprint (No Extra Code Needed)

One thing we really like about this approach is that we don't have to write separate logic for different biometric types.

The same implementation works for:

  • Fingerprint (Android)
  • Face ID (iOS)
  • Touch ID (older iOS devices)

The local_auth package automatically detects and uses the available biometric method on the device.

So whether the user is using Face ID or fingerprint - the flow remains exactly the same from our side.

What We Actually Add in Production Apps

In real apps, we don't stop at just authentication:

  • ✅ Store a flag (biometric enabled/disabled) using secure storage
  • ✅ Provide fallback (PIN or password)
  • ✅ Show a toggle in settings
  • ✅ Auto-trigger biometrics on app launch (if enabled)

Mistakes We've Seen While Implementing This

This is where most implementations go wrong 👇

1. Not Handling Unsupported Devices

Many developers assume biometrics will always be available.

👉 Always check:

✅ canCheckBiometrics
✅ isDeviceSupported

2. Ignoring iOS Permission Setup

Missing NSFaceIDUsageDescription is one of the most common issues.

👉 Result: App crash or silent failure.

3. Not Providing a Fallback Option

  • Biometrics can fail due to:
  • Wet fingers
  • Face not recognized
  • Sensor issues

👉 Always provide:

  • PIN and Password fallback

4. Forcing Biometric Without User Consent

Never enable biometrics by default.

👉 Always:

  • Ask user permission
  • Let them enable/disable in settings

5. Poor Error Handling

Most developers just return false on failure.

👉 Instead:

  • Handle exceptions properly
  • Show meaningful messages to users

6. Not Testing Edge Cases

Biometric flows behave differently when:

  • App goes to background
  • Device is locked/unlocked
  • Multiple failed attempts occur

👉 Always test:

  • Background/foreground transitions
  • Lock screen scenarios

7. Using Biometrics as the Only Security Layer

Biometric authentication should not replace backend authentication.

👉 Always combine with:

  • Secure tokens
  • Backend validation

Things That Helped Us Get This Right

From our experience, this is what works best:

  • Keep biometric logic separate (service class)
  • Always provide fallback authentication
  • Respect user choice and privacy
  • Test on real devices (not just emulators)
  • Keep UX smooth and non-intrusive

Final Thoughts

Biometric authentication is powerful - but only when implemented thoughtfully.

It's not just about adding a fingerprint or Face ID button. It's about creating a secure, seamless, and reliable user experience.

When done right, it can significantly improve both security and user satisfaction.

If you're building a Flutter app and want to level up your authentication flow, this is one feature you shouldn't skip.

Top comments (0)