image

14 Dec 2025

9K

35K

Building Login with Face ID / Fingerprint in Flutter

Biometric authentication, such as Face ID and fingerprint scanning, offers a secure and convenient way for users to log into applications. Integrating these features into your Flutter app enhances security and improves the user experience by eliminating the need to remember complex passwords. This article will guide you through the process of implementing biometric authentication in a Flutter application using the local_auth package.

Prerequisites

  • A basic understanding of Flutter development.
  • Flutter SDK installed.
  • An IDE (VS Code or Android Studio).
  • A physical device or an emulator/simulator with biometric capabilities configured.

Step 1: Project Setup

First, you need to add the local_auth package to your pubspec.yaml file. This package provides a cross-platform way to interact with biometric sensors.


dependencies:
  flutter:
    sdk: flutter
  local_auth: ^2.1.8 # Use the latest stable version

After adding the dependency, run flutter pub get in your terminal to fetch the package.

Step 2: Platform-Specific Configuration

Biometric authentication requires specific permissions and configurations on both iOS and Android platforms.

iOS Configuration

For iOS, you need to add a usage description to your Info.plist file, explaining why your app requires biometric authentication. This description will be shown to the user when the app requests access to Face ID or Touch ID.

Open ios/Runner/Info.plist and add the following key-value pair:


<key>NSFaceIDUsageDescription</key>
<string>Why is my app using Face ID? For example: To quickly authenticate you for secure access to your account.</string>

Android Configuration

For Android, you need to declare the USE_BIOMETRIC permission in your AndroidManifest.xml file. This permission is necessary for the app to access biometric hardware.

Open android/app/src/main/AndroidManifest.xml and add the following permission tag inside the <manifest> tag, but outside the <application> tag:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.your_app_name">

    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
    <!-- Optionally, for older Android versions, you might need USE_FINGERPRINT -->
    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>

    <application
        android:label="your_app_name"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <!-- ... other application configurations ... -->
    </application>
</manifest>

Step 3: Implementing Biometric Authentication

Now, let's write the Flutter code to interact with the biometric sensors.

Import the Package

First, import the local_auth package in your Dart file:


import 'package:local_auth/local_auth.dart';
import 'package:flutter/services.dart'; // For PlatformException

Check Biometric Availability

Before attempting to authenticate, it's good practice to check if biometrics are available on the device and if the user has enrolled any biometrics.


final LocalAuthentication auth = LocalAuthentication();
bool _canCheckBiometrics = false;
List<BiometricType> _availableBiometrics = [];

Future<void> _checkBiometrics() async {
  late bool canCheckBiometrics;
  try {
    canCheckBiometrics = await auth.canCheckBiometrics;
  } on PlatformException catch (e) {
    canCheckBiometrics = false;
    print(e);
  }
  if (!mounted) {
    return;
  }

  setState(() {
    _canCheckBiometrics = canCheckBiometrics;
  });
}

Future<void> _getAvailableBiometrics() async {
  late List<BiometricType> availableBiometrics;
  try {
    availableBiometrics = await auth.getAvailableBiometrics();
  } on PlatformException catch (e) {
    availableBiometrics = <BiometricType>[];
    print(e);
  }
  if (!mounted) {
    return;
  }

  setState(() {
    _availableBiometrics = availableBiometrics;
  });
}

Authenticate the User

The core function for authentication is authenticate(). It takes a localizedReason which is the message displayed to the user during the biometric prompt.


Future<void> _authenticate() async {
  bool authenticated = false;
  try {
    authenticated = await auth.authenticate(
      localizedReason: 'Scan your fingerprint or face to authenticate',
      options: const AuthenticationOptions(
        stickyAuth: true, // Keep the authentication dialog visible until dismissed or authenticated
        useErrorDialogs: true, // Show platform specific error messages
      ),
    );
  } on PlatformException catch (e) {
    print(e);
    // Handle specific errors like not enrolled, locked out, etc.
    return;
  }
  if (!mounted) {
    return;
  }

  // Update UI based on 'authenticated' status
  setState(() {
    // For example, navigate to home screen or show success message
    if (authenticated) {
      print("Authentication successful!");
    } else {
      print("Authentication failed!");
    }
  });
}

Full Example: Login Widget

Here's a simple StatefulWidget demonstrating how to integrate these methods into a login screen.


import 'package:flutter/material.dart';
import 'package:local_auth/local_auth.dart';
import 'package:flutter/services.dart';

class BiometricLoginPage extends StatefulWidget {
  const BiometricLoginPage({Key? key}) : super(key: key);

  @override
  State<BiometricLoginPage> createState() => _BiometricLoginPageState();
}

class _BiometricLoginPageState extends State<BiometricLoginPage> {
  final LocalAuthentication auth = LocalAuthentication();
  bool _canCheckBiometrics = false;
  List<BiometricType> _availableBiometrics = [];
  String _authorized = 'Not Authorized';

  @override
  void initState() {
    super.initState();
    _checkBiometrics();
    _getAvailableBiometrics();
  }

  Future<void> _checkBiometrics() async {
    late bool canCheckBiometrics;
    try {
      canCheckBiometrics = await auth.canCheckBiometrics;
    } on PlatformException catch (e) {
      canCheckBiometrics = false;
      print(e);
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _canCheckBiometrics = canCheckBiometrics;
    });
  }

  Future<void> _getAvailableBiometrics() async {
    late List<BiometricType> availableBiometrics;
    try {
      availableBiometrics = await auth.getAvailableBiometrics();
    } on PlatformException catch (e) {
      availableBiometrics = <BiometricType>[];
      print(e);
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _availableBiometrics = availableBiometrics;
    });
  }

  Future<void> _authenticate() async {
    bool authenticated = false;
    try {
      setState(() {
        _authorized = 'Authenticating...';
      });
      authenticated = await auth.authenticate(
        localizedReason: 'Please authenticate to access your account',
        options: const AuthenticationOptions(
          stickyAuth: true,
          useErrorDialogs: true,
        ),
      );
    } on PlatformException catch (e) {
      print(e);
      setState(() {
        _authorized = 'Error: ${e.message}';
      });
      return;
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _authorized = authenticated ? 'Authorized' : 'Not Authorized';
      if (authenticated) {
        // Navigate to the home screen or perform other actions
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Authentication Successful!')),
        );
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Authentication Failed!')),
        );
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Biometric Login'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Can check biometrics: $_canCheckBiometrics'),
            Text('Available biometrics: ${_availableBiometrics.join(', ')}'),
            Text('Current authorization status: $_authorized'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _canCheckBiometrics && _availableBiometrics.isNotEmpty
                  ? _authenticate
                  : null, // Disable button if biometrics not available
              child: const Text('Authenticate with Biometrics'),
            ),
            const SizedBox(height: 10),
            TextButton(
              onPressed: () {
                // Handle traditional login (username/password)
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('Proceeding to traditional login')),
                );
              },
              child: const Text('Use Password Login'),
            ),
          ],
        ),
      ),
    );
  }
}

Step 4: Handling Authentication States and Errors

It's crucial to provide clear feedback to the user about the authentication status:

  • Success: Navigate the user to the main application screen.
  • Failure: Inform the user that authentication failed and perhaps offer an alternative login method (e.g., password).
  • Not Available/Enrolled: If _canCheckBiometrics is false or _availableBiometrics is empty, disable the biometric login button and suggest traditional login methods.
  • Platform Exceptions: The authenticate method can throw PlatformException for various reasons (e.g., biometrics not enrolled, device security features not set up, user cancelled, too many failed attempts). Catching these exceptions allows you to provide more specific error messages to the user.

You can also offer an option for users to switch to traditional password-based login if they prefer not to use biometrics or if biometric authentication fails multiple times.

Conclusion

Integrating Face ID or fingerprint authentication significantly enhances the user experience and security of your Flutter application. By following these steps, you can successfully implement biometric login, making your app more convenient and secure for your users. Remember to thoroughly test your implementation on various devices and platforms to ensure a robust and reliable solution.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is