๐ Biometric Login in Flutter (Riverpod + Bloc)

If you're building a fintech, banking, or wallet app, biometric login must be secure, structured, testable, cleanly architected, and token-safe.
This guide covers a Riverpod-based implementation, a Bloc-based implementation, fintech-grade secure architecture, secure token flow, and the architecture diagram behind it.
Fintech-Grade Secure Architecture
Before writing code, understand the security flow.

Secure biometric login flow (production pattern):
First-time login โ user logs in via email/password, backend returns an access token and a refresh token, tokens are stored using flutter_secure_storage, and the user enables "Biometric Login."
Next app launch โ the app checks if a token exists, triggers the biometric prompt, and on success unlocks the stored token and navigates to the dashboard. On failure, it shows a PIN/password fallback.
Clean architecture layers:
Presentation (UI + Riverpod/Bloc)
โ
Domain (UseCases)
โ
Data (Repositories)
โ
Local Auth + Secure Storage
โ
Backend API
This separation makes your app testable, scalable, and enterprise-ready.
1. Riverpod Biometric Implementation
Riverpod works beautifully for secure state management.
Folder structure:
lib/
โโโ core/
โ โโโ biometric_service.dart
โ โโโ secure_storage_service.dart
โโโ features/auth/
โ โโโ auth_repository.dart
โ โโโ biometric_provider.dart
โ โโโ login_screen.dart
Biometric service:
import 'package:local_auth/local_auth.dart';
class BiometricService {
final LocalAuthentication _auth = LocalAuthentication();
Future<bool> authenticate() async {
try {
return await _auth.authenticate(
localizedReason: "Authenticate to access your account",
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
} catch (_) {
return false;
}
}
}
Secure storage service:
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
final _storage = const FlutterSecureStorage();
Future<void> saveToken(String token) async {
await _storage.write(key: "auth_token", value: token);
}
Future<String?> getToken() async {
return await _storage.read(key: "auth_token");
}
Future<void> clear() async {
await _storage.deleteAll();
}
}
Riverpod providers:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/biometric_service.dart';
import '../../core/secure_storage_service.dart';
final biometricServiceProvider = Provider((ref) => BiometricService());
final secureStorageProvider = Provider((ref) => SecureStorageService());
final biometricAuthProvider =
StateNotifierProvider<BiometricAuthNotifier, AsyncValue<bool>>((ref) {
return BiometricAuthNotifier(ref);
});
class BiometricAuthNotifier extends StateNotifier<AsyncValue<bool>> {
final Ref ref;
BiometricAuthNotifier(this.ref) : super(const AsyncValue.data(false));
Future<void> authenticate() async {
state = const AsyncValue.loading();
final biometric = ref.read(biometricServiceProvider);
final storage = ref.read(secureStorageProvider);
final token = await storage.getToken();
if (token == null) {
state = const AsyncValue.error("No stored token", StackTrace.empty);
return;
}
final success = await biometric.authenticate();
state = AsyncValue.data(success);
}
}
Riverpod login UI:
class LoginScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(biometricAuthProvider);
return Scaffold(
body: Center(
child: authState.when(
data: (success) => success
? const Text("Welcome Back!")
: ElevatedButton(
onPressed: () =>
ref.read(biometricAuthProvider.notifier).authenticate(),
child: const Text("Login with Biometrics"),
),
loading: () => const CircularProgressIndicator(),
error: (e, _) => Text("Error: $e"),
),
),
);
}
}
2. Bloc Biometric Implementation
Bloc is preferred in larger fintech systems with event-driven flows.
flutter_bloc: ^8.1.0
Bloc events:
abstract class BiometricEvent {}
class BiometricLoginRequested extends BiometricEvent {}
Bloc states:
abstract class BiometricState {}
class BiometricInitial extends BiometricState {}
class BiometricLoading extends BiometricState {}
class BiometricSuccess extends BiometricState {}
class BiometricFailure extends BiometricState {
final String message;
BiometricFailure(this.message);
}
Bloc implementation:
class BiometricBloc extends Bloc<BiometricEvent, BiometricState> {
final BiometricService biometricService;
final SecureStorageService storage;
BiometricBloc(this.biometricService, this.storage)
: super(BiometricInitial()) {
on<BiometricLoginRequested>(_onLoginRequested);
}
Future<void> _onLoginRequested(
BiometricLoginRequested event, Emitter emit) async {
emit(BiometricLoading());
final token = await storage.getToken();
if (token == null) {
emit(BiometricFailure("No token found"));
return;
}
final success = await biometricService.authenticate();
if (success) {
emit(BiometricSuccess());
} else {
emit(BiometricFailure("Authentication failed"));
}
}
}
Bloc UI:
BlocBuilder<BiometricBloc, BiometricState>(
builder: (context, state) {
if (state is BiometricLoading) {
return const CircularProgressIndicator();
} else if (state is BiometricSuccess) {
return const Text("Welcome!");
} else if (state is BiometricFailure) {
return Text(state.message);
}
return ElevatedButton(
onPressed: () =>
context.read<BiometricBloc>().add(BiometricLoginRequested()),
child: const Text("Login with Biometrics"),
);
},
)
Fintech Security Enhancements (Advanced)
- Enable
biometricOnlyin fintech apps โbiometricOnly: true. - Use device integrity checks โ Android Play Integrity API, iOS DeviceCheck API.
- Protect against rooted/jailbroken devices โ packages like
flutter_jailbreak_detection. - Use short token expiry โ access token 15 min, refresh token securely stored.
- Logout if biometric fails multiple times.
Riverpod or Bloc โ Which Fits Fintech Better?
Both are production-grade choices. Riverpod tends to feel lighter for smaller, focused auth flows; Bloc's explicit event/state model tends to fit larger, event-driven fintech systems with many moving parts better. The right choice depends on your team's existing architecture more than the feature itself.
Interview-Level Talking Points
If asked "How would you implement biometric login in a fintech app?" โ use secure storage for tokens, never store the password locally, use biometrics only after first login, provide a fallback mechanism, handle locked-out cases, separate presentation and domain layers, and use DI with a testable architecture.
That answer immediately sounds senior-level.
Final Thoughts
Biometric login is easy to implement. But fintech-grade biometric login requires clean architecture, secure token management, proper state management (Riverpod/Bloc), security edge case handling, and device integrity validation.
No spam, no schedule โ just an email when a new post goes up.