Perché l'autenticazione biometrica
L'autenticazione biometrica è diventata uno standard di fatto per proteggere l'accesso ad aree sensibili delle applicazioni mobili: dati bancari, note private, gestori di password. Rispetto a un PIN, offre un'esperienza utente più fluida e un buon livello di sicurezza, poiché i dati biometrici non lasciano mai il secure enclave del dispositivo.
In Flutter, il pacchetto ufficiale local_auth permette di sfruttare Face ID, Touch ID e il riconoscimento tramite impronta digitale con poche righe di codice.
Installazione
Aggiungi le dipendenze al file pubspec.yaml:
dependencies:
local_auth: ^2.3.0
local_auth_android: ^1.0.46
local_auth_ios: ^1.1.7
Configurazione nativa
iOS
Aggiungi la chiave NSFaceIDUsageDescription al file ios/Runner/Info.plist, altrimenti l'app verrà rifiutata dall'App Store:
<key>NSFaceIDUsageDescription</key>
<string>Utilizziamo Face ID per proteggere l'accesso ai tuoi dati.</string>
Android
Nel file android/app/src/main/AndroidManifest.xml verifica il permesso:
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
Inoltre la MainActivity deve estendere FlutterFragmentActivity (e non FlutterActivity), altrimenti la chiamata biometrica va in crash:
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterFragmentActivity()
Verificare la disponibilità
Prima di autenticare, è buona norma controllare se il dispositivo supporta la biometria e quali metodi sono disponibili:
import 'package:local_auth/local_auth.dart';
final LocalAuthentication auth = LocalAuthentication();
Future<bool> isBiometricAvailable() async {
final bool canCheck = await auth.canCheckBiometrics;
final bool isSupported = await auth.isDeviceSupported();
return canCheck && isSupported;
}
Future<List<BiometricType>> getAvailableBiometrics() async {
return await auth.getAvailableBiometrics();
}
Il metodo getAvailableBiometrics() restituisce una lista di tipi come BiometricType.face, BiometricType.fingerprint o BiometricType.strong, utile per mostrare l'icona corretta nell'interfaccia.
Eseguire l'autenticazione
Il cuore del pacchetto è il metodo authenticate:
Future<bool> authenticate() async {
try {
return await auth.authenticate(
localizedReason: 'Conferma la tua identità per continuare',
options: const AuthenticationOptions(
biometricOnly: false, // consente il fallback a PIN/pattern
stickyAuth: true, // riprende dopo un'interruzione
useErrorDialogs: true,
),
);
} on PlatformException catch (e) {
debugPrint('Errore biometrico: ${e.code} - ${e.message}');
return false;
}
}
Alcune opzioni importanti:
biometricOnly: setrue, esclude il fallback al codice di sblocco del dispositivo.stickyAuth: mantiene attiva l'autenticazione se l'app va in background (ad esempio quando l'utente cambia app per aprire un'altra applicazione).useErrorDialogs: mostra le finestre di dialogo di sistema per situazioni come l'assenza di biometria registrata.
Gestione degli errori
Le PlatformException restituiscono codici specifici che è opportuno gestire. I più comuni sono definiti nella classe auth_error:
import 'package:local_auth/error_codes.dart' as auth_error;
try {
await auth.authenticate(localizedReason: 'Accedi');
} on PlatformException catch (e) {
switch (e.code) {
case auth_error.notAvailable:
// Nessuna biometria disponibile sul dispositivo
break;
case auth_error.notEnrolled:
// L'utente non ha registrato impronte o volto
break;
case auth_error.lockedOut:
case auth_error.permanentlyLockedOut:
// Troppi tentativi falliti
break;
}
}
Un esempio completo
class AuthGate extends StatefulWidget {
const AuthGate({super.key});
@override
State<AuthGate> createState() => _AuthGateState();
}
class _AuthGateState extends State<AuthGate> {
final LocalAuthentication _auth = LocalAuthentication();
bool _autenticato = false;
Future<void> _sblocca() async {
final bool disponibile =
await _auth.canCheckBiometrics && await _auth.isDeviceSupported();
if (!disponibile) return;
try {
final ok = await _auth.authenticate(
localizedReason: 'Sblocca per accedere ai tuoi dati',
options: const AuthenticationOptions(stickyAuth: true),
);
if (mounted) setState(() => _autenticato = ok);
} on PlatformException catch (e) {
debugPrint('Errore: ${e.code}');
}
}
@override
Widget build(BuildContext context) {
if (_autenticato) {
return const HomeScreen();
}
return Scaffold(
body: Center(
child: ElevatedButton.icon(
onPressed: _sblocca,
icon: const Icon(Icons.fingerprint),
label: const Text('Sblocca app'),
),
),
);
}
}
Best practice
- Non affidare la sicurezza solo alla biometria: usala come gate di accesso, ma conserva i dati sensibili con soluzioni come
flutter_secure_storage. - Offri sempre un'alternativa: mantieni un metodo di fallback (PIN dell'app o codice del dispositivo) per gli utenti senza biometria registrata.
- Ri-autentica dopo il background: per app critiche, richiedi nuovamente l'autenticazione quando l'app torna in foreground usando il
WidgetsBindingObserver. - Rispetta la privacy: comunica chiaramente all'utente perché richiedi l'accesso biometrico tramite il parametro
localizedReason.
Conclusioni
Con local_auth integrare Face ID, Touch ID e impronte digitali in Flutter richiede poche righe di codice, ma la parte più delicata riguarda la configurazione nativa e la gestione robusta degli errori. Combinando l'autenticazione biometrica con uno storage sicuro otterrai un livello di protezione adeguato per le informazioni più sensibili dei tuoi utenti.
