Setting Up the Biometric Auth SDK
What you'll learn
- Generating the Biometric Auth SDK with API/Impl split
- Configuring platform permissions on iOS and Android
- Using biometric as an unlock gate, not a primary credential
- Restoring a session after successful biometric verification
Prerequisites
- An existing Archipelago monorepo (see Monorepo Scaffolding)
- Auth SDK already generated and wired up (see Auth SDK Setup)
Pro Feature
biometric_auth_sdk requires an active Pro subscription. Run archipelago auth status to confirm your tier.
Step 1: Generate the Biometric Auth SDK
archipelago generate biometric_auth_sdkNo variables are prompted — the brick generates a fixed structure with sane defaults.
Step 2: Understand the Generated Structure
The brick uses the API/Impl split because other features (e.g. a locked settings screen) may need to depend on the biometric contract without pulling in the implementation:
features/biometric_auth/
├── biometric_auth_api/
│ └── lib/src/
│ ├── biometric_auth_sdk.dart # Abstract SDK contract
│ └── models/
│ └── biometric_result.dart # Success/failure sealed class
└── biometric_auth_impl/
└── lib/src/
├── biometric_auth_sdk_impl.dart # local_auth wrapper
└── di/ # GetIt moduleStep 3: Configure iOS Permissions
Add the Face ID usage description to ios/Runner/Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>We use Face ID to quickly verify your identity.</string>Touch ID does not require a separate plist key, but the string above is shown for both Face ID and Touch ID prompts on devices that support them.
Step 4: Configure Android Permissions
For devices running API 23–27, add the fingerprint permission to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.USE_FINGERPRINT" />Devices on API 28+ use USE_BIOMETRIC which the underlying local_auth package declares automatically. You only need this explicit declaration for older API levels.
Step 5: Register the Feature
In your shell app's bootstrap.dart, register the biometric SDK alongside your other features:
import 'package:biometric_auth_impl/biometric_auth_impl.dart';
FeatureRegistry.register(BiometricAuthImpl());Features that gate behaviour behind biometric verification depend on biometric_auth_api only:
# In a dependent feature's pubspec.yaml
dependencies:
biometric_auth_api:
path: ../../features/biometric_auth/biometric_auth_apiStep 6: Check Availability
Before presenting a biometric prompt, always check whether the device supports it:
final biometricSDK = getIt<BiometricAuthSDK>();
final isSupported = await biometricSDK.isAvailable();
if (!isSupported) {
// Fall back to PIN / password flow
return;
}Android Emulators
isAvailable() returns false on Android emulators that have no enrolled biometrics. Enroll a fingerprint via Settings > Security > Fingerprint inside the emulator, or test on a physical device.
Step 7: Trigger Authentication and Restore the Session
Biometric is an unlock gate, not a primary credential. The SDK verifies the user's identity locally — it does not issue a token. After a successful result, your code is responsible for restoring the app session by calling AuthSDK.loginSuccess():
final result = await biometricSDK.authenticate(
reason: 'Verify your identity to continue',
);
if (result.isSuccess) {
// Biometric passed — restore the cached session
await getIt<AuthSDK>().loginSuccess(userId: cachedUserId);
} else {
// result.errorMessage contains a localised failure reason
showErrorSnackbar(result.errorMessage);
}The cachedUserId should come from your local secure storage (written during the initial password-based login), not from the biometric result itself.
Step 8: Typical Integration — App Resume Guard
A common pattern is to show a biometric prompt whenever the app resumes after being backgrounded:
class AppLifecycleGuard extends StatefulWidget {
const AppLifecycleGuard({required this.child, super.key});
final Widget child;
@override
State<AppLifecycleGuard> createState() => _AppLifecycleGuardState();
}
class _AppLifecycleGuardState extends State<AppLifecycleGuard>
with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_promptBiometric();
}
}
Future<void> _promptBiometric() async {
final sdk = getIt<BiometricAuthSDK>();
if (!await sdk.isAvailable()) return;
final result = await sdk.authenticate(
reason: 'Confirm it\'s you to continue',
);
if (result.isSuccess) {
await getIt<AuthSDK>().loginSuccess(userId: cachedUserId);
}
}
@override
Widget build(BuildContext context) => widget.child;
}Common Customizations
| Customization | Where to Change |
|---|---|
| Custom prompt copy | Pass a different reason string to authenticate() |
| Disable on tablets | Wrap isAvailable() with a device-type check |
| Skip on debug builds | Guard with kDebugMode in your resume guard |
| Biometric + PIN fallback | Show PIN screen when result.isFallback is true |
Next Steps
- Set up Connectivity SDK to handle offline states
- Configure monitoring to track biometric failure rates