# How to Create a Secure Login System with Biometric Authentication in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 25-35 min
- Compatibility: FlutterFlow Pro+ (code export required)
- Last updated: March 2026

## TL;DR

Add biometric authentication as a login shortcut for returning users. Use the local_auth package in a Custom Action to check device biometric capabilities and authenticate with fingerprint or face ID. Store credentials securely using flutter_secure_storage after the initial email/password login. On subsequent app launches, prompt biometric verification and auto-login with stored credentials. Include a settings toggle to enable or disable biometric login and a PIN fallback after three failed biometric attempts.

## Adding Biometric Login to Your FlutterFlow App

Biometric authentication lets returning users unlock your app with fingerprint or face recognition instead of typing their password every time. This tutorial implements biometric login as a convenience shortcut on top of standard email/password authentication. The initial login uses email/password, and biometric is offered as an optional fast unlock for subsequent sessions.

## Before you start

- A FlutterFlow project with Firebase Authentication (email/password) configured
- The local_auth and flutter_secure_storage packages added as dependencies
- A physical device or emulator with biometric capabilities for testing
- Basic familiarity with FlutterFlow Custom Actions

## Step-by-step guide

### 1. Check device biometric capabilities with a Custom Action

Create a Custom Action checkBiometricSupport that imports the local_auth package. Call LocalAuthentication().canCheckBiometrics and LocalAuthentication().isDeviceSupported() to verify the device has biometric hardware and at least one biometric enrolled. Return a boolean indicating support. Call this action on app startup and store the result in App State isBiometricAvailable. Use this value to conditionally show or hide biometric-related UI elements throughout the app.

```
// Custom Action: checkBiometricSupport
import 'package:local_auth/local_auth.dart';

Future<bool> checkBiometricSupport() async {
  final auth = LocalAuthentication();
  try {
    final canCheck = await auth.canCheckBiometrics;
    final isSupported = await auth.isDeviceSupported();
    if (!canCheck || !isSupported) return false;
    final available = await auth.getAvailableBiometrics();
    return available.isNotEmpty;
  } catch (e) {
    return false;
  }
}
```

**Expected result:** The app detects whether the device supports biometric authentication and stores the result in App State.

### 2. Store credentials securely after initial email/password login

After a successful email/password login, if the user has enabled biometric login (App State isBiometricEnabled == true), create a Custom Action storeCredentials that uses flutter_secure_storage to save the email and a refresh token. The secure storage encrypts data using the device keychain (iOS) or KeyStore (Android). Never store the raw password; store the Firebase refresh token instead, which can be used to re-authenticate. Write: storage.write(key: 'user_email', value: email) and storage.write(key: 'refresh_token', value: token).

```
// Custom Action: storeCredentials
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

Future<void> storeCredentials(
  String email, String refreshToken) async {
  const storage = FlutterSecureStorage();
  await storage.write(key: 'user_email', value: email);
  await storage.write(key: 'refresh_token', value: refreshToken);
}
```

**Expected result:** Login credentials are encrypted and stored in the device secure storage after the first successful login.

### 3. Build the biometric prompt screen and authentication flow

Create a BiometricPromptPage that appears on app launch when isBiometricEnabled is true. Display a fingerprint or face Icon (large, centered), a Text ('Verify your identity'), and a 'Use Password Instead' TextButton below. On Page Load, trigger a Custom Action authenticateBiometric that calls LocalAuthentication().authenticate(localizedReason: 'Verify your identity to access the app'). On success, read stored credentials from flutter_secure_storage, use the refresh token to re-authenticate with Firebase, and navigate to the home page. On failure, increment a failedAttempts Page State counter. After 3 failures, navigate to the standard login page.

```
// Custom Action: authenticateBiometric
import 'package:local_auth/local_auth.dart';

Future<bool> authenticateBiometric() async {
  final auth = LocalAuthentication();
  try {
    return await auth.authenticate(
      localizedReason: 'Verify your identity to access the app',
      options: const AuthenticationOptions(
        stickyAuth: true,
        biometricOnly: false,
      ),
    );
  } catch (e) {
    return false;
  }
}
```

**Expected result:** The device biometric prompt appears on app launch. Successful verification auto-logs in the user.

### 4. Add the biometric toggle in the settings page

On your SettingsPage, add a Row with Text ('Biometric Login') and a Switch widget. Show this row with Conditional Visibility only when App State isBiometricAvailable is true. On Switch toggle ON: first authenticate with biometric to confirm the user owns the biometric, then set App State isBiometricEnabled to true and store current credentials using the storeCredentials Custom Action. On Switch toggle OFF: set isBiometricEnabled to false and delete stored credentials from flutter_secure_storage using storage.deleteAll(). Persist isBiometricEnabled in the user Firestore document so the setting survives app reinstalls.

**Expected result:** Users can enable or disable biometric login from settings, with the toggle only visible on supported devices.

### 5. Implement PIN fallback for failed biometric attempts

Create a PINEntryPage as a fallback after 3 failed biometric attempts or when biometric hardware is temporarily unavailable. Use a Column with a Text ('Enter your PIN'), four Container boxes showing entered digits as dots, and a custom number pad Grid of Containers (1-9, 0, backspace, confirm). Store the PIN hash (not plain text) in flutter_secure_storage when the user first sets it up. On PIN entry, hash the input and compare with the stored hash. On match, read credentials from secure storage and auto-login. After 5 failed PIN attempts, require full email/password login and clear stored credentials.

**Expected result:** A PIN entry screen provides a secure fallback when biometric authentication fails.

## Complete code example

File: `FlutterFlow Biometric Auth Setup`

```text
PACKAGES REQUIRED:
  local_auth: ^2.1.0
  flutter_secure_storage: ^9.0.0

APP STATE:
  isBiometricAvailable: Boolean (set on app start)
  isBiometricEnabled: Boolean (persisted, user preference)

APP LAUNCH FLOW:
  1. Custom Action: checkBiometricSupport()
     → Set App State isBiometricAvailable
  2. If isBiometricEnabled == true:
     → Navigate to BiometricPromptPage
  3. Else:
     → Navigate to standard LoginPage

BIOMETRIC PROMPT PAGE:
  Column (centered)
    ├── Icon (fingerprint, size 80)
    ├── Text ("Verify your identity")
    ├── Text ("Use Password Instead", link)
    On Page Load:
      → authenticateBiometric()
      → If success: readCredentials() → autoLogin() → HomePage
      → If fail: increment failedAttempts
      → If failedAttempts >= 3: navigate to PINEntryPage

SETTINGS PAGE:
  Row (Conditional: isBiometricAvailable)
    ├── Text ("Biometric Login")
    └── Switch
          On Toggle ON:
            1. authenticateBiometric() to confirm
            2. storeCredentials(email, refreshToken)
            3. Set isBiometricEnabled = true
          On Toggle OFF:
            1. clearCredentials()
            2. Set isBiometricEnabled = false

PIN FALLBACK PAGE:
  Column
    ├── Text ("Enter your PIN")
    ├── Row (4 dot indicators)
    └── Grid (number pad 0-9 + backspace + confirm)
          On Confirm: hash(input) == stored hash
            → If match: autoLogin()
            → If 5 failures: navigate to LoginPage

SECURITY RULES:
  - Never store plain-text passwords
  - Use device secure storage (keychain/keystore)
  - Hash PINs with SHA-256
  - Clear credentials on biometric disable
  - Require re-auth after 3 biometric failures
```

## Common mistakes

- **Using biometric as the only authentication method without an initial login** — Biometric only verifies the person holding the device, not their account identity. Without a prior email/password login, there is no way to associate the biometric with a specific user account. Fix: Always require an initial email/password login first. Biometric serves as a convenience shortcut for subsequent sessions, not a replacement for primary authentication.
- **Storing the user password in plain text for auto-login after biometric** — If the device secure storage is compromised, the plain-text password is exposed. This violates basic security principles. Fix: Store the Firebase refresh token instead of the password. Refresh tokens can be revoked server-side if compromised, limiting the damage.
- **Not providing a fallback when biometric fails** — Biometric can fail due to wet fingers, sunglasses, hardware issues, or software glitches. Without a fallback, users are locked out of the app. Fix: Implement a PIN fallback after 3 failed biometric attempts, and always show a 'Use Password Instead' option that navigates to the standard login page.

## Best practices

- Use biometric as a login shortcut, not a replacement for primary email/password authentication
- Store refresh tokens in flutter_secure_storage, never plain-text passwords
- Check canCheckBiometrics and isDeviceSupported before showing biometric UI
- Provide a PIN fallback and a password fallback for when biometric fails
- Hash PINs with SHA-256 before storing, never compare or store plain-text PINs
- Clear all stored credentials when the user disables biometric login
- Require biometric verification when the user enables the feature to confirm device ownership

## Frequently asked questions

### Does biometric authentication work on both iOS and Android?

Yes. The local_auth package supports Face ID and Touch ID on iOS, and fingerprint and face recognition on Android. The authenticate() method automatically uses whatever biometric is available on the device.

### What happens if the user changes their biometric on the device?

When a user adds or removes a fingerprint/face on the device, the operating system invalidates existing biometric enrollments. Your app should handle the resulting authentication failure by falling back to PIN or password login.

### Can I use biometric to authorize specific actions like payments?

Yes. Call authenticateBiometric() before any sensitive action (payment, password change, data export). The localizedReason parameter lets you customize the prompt message for each use case.

### Is flutter_secure_storage truly secure?

Yes, it uses the iOS Keychain and Android KeyStore, which are hardware-backed encrypted storage. Data is encrypted at rest and tied to the device. It is the standard approach for storing sensitive data in Flutter apps.

### What if the device does not have biometric hardware?

The checkBiometricSupport Custom Action detects this and sets isBiometricAvailable to false. The biometric toggle in settings will not appear, and the app uses standard email/password login exclusively.

### Can RapidDev help implement advanced authentication systems?

Yes. RapidDev can build multi-factor authentication combining biometric, PIN, email verification, and SMS OTP for maximum security, along with session management and device trust policies.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-secure-login-system-with-biometric-authentication-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-secure-login-system-with-biometric-authentication-in-flutterflow
