Flutter PUA Auth Plugin
A Flutter plugin that integrates The Whisper Company's PUA SDK for iOS and Android, providing biometric authentication and continuous face monitoring capabilities for enhanced security.
📋 Table of Contents
- Overview
- Features
- Platform Support
- Platform Feature Comparison
- Installation
- Setup & Configuration
- API Reference
- Usage Examples
- Architecture
- Simulator/Emulator Support
- Important Notes
- Troubleshooting
- License
- Demo App
Overview
The Flutter PUA Auth plugin serves as a bridge between Flutter applications and native PUA SDKs (iOS and Android), offering:
- Biometric Authentication: Face ID, Touch ID, Fingerprint authentication
- Continuous Face Monitoring: Real-time face detection and counting from camera feed
- Automatic Screen Locking: Lock/unlock based on face detection state
- Multi-Face Detection: Detect and handle multiple faces (shoulder surfing protection)
- Configurable Settings: Customize refresh rate, eyes-off-screen time, and maximum allowed faces
Features
🔐 Biometric Authentication
- iOS: Face ID, Touch ID
- Android: Fingerprint, Face Unlock
- Automatic fallback to platform biometric APIs when PUA SDK is unavailable
👁️ Continuous Face Monitoring
- Real-time face count detection from camera feed
- Automatic screen locking when:
- No face detected (user left)
- Multiple faces detected (shoulder surfing)
- Automatic unlocking when authorized user returns
- Configurable monitoring parameters
⚙️ Configurable Parameters
- Refresh Rate:
Instant,fast,medium,light(default:Instant) - Eyes Off Screen Time: 0.25-30.0 seconds (default: 10.0)
- Number of Faces Allowed: Minimum 1, no maximum (default: 1)
- Low Light Threshold: 200-2000 lux (default: 400.0)
Platform Support
| Platform | Status | Notes |
|---|---|---|
| iOS (Real Device) | ✅ Fully Supported | All features work: PUA SDK face monitoring, biometric auth |
| Android (Real Device) | ✅ Fully Supported | All features work: PUA SDK face monitoring, biometric auth |
| iOS Simulator | ⚠️ Limited Support | See Simulator/Emulator Behavior below |
| Android Emulator | ⚠️ Limited Support | See Simulator/Emulator Behavior below |
Platform Feature Comparison
| Feature | iOS | Android | Notes |
|---|---|---|---|
| Version Detection | ✅ Working | ❌ Not Available | Android SDK doesn't expose method |
refreshRate |
✅ Working | ⚠️ Partial | May not be set correctly |
eyesOffScreenTime |
✅ Working (2.0) | ❌ Not Supported | Android uses eyeClosedProbabilityThreshold |
eyeClosedProbabilityThreshold |
❌ Not Available | ✅ Working | iOS uses timeout-based approach |
numberOfFacesAllowed |
✅ Working (2.0) | ✅ Working | Set via PUAOne/PWATwo sub-objects |
lowLightThreshold |
✅ Working | ⚠️ Partial | May not be set correctly |
onLightWarning callback |
✅ Working | ❌ Not Available | Android SDK doesn't have this method |
onAuthSuccess |
✅ Working | ✅ Working | Both platforms fully supported |
onAuthFailed |
✅ Working | ✅ Working | Both platforms fully supported |
| Face count accuracy | ✅ Exact | ⚠️ Estimated | Android estimates for IntruderFaceDetected |
authenticateUser() face auth |
✅ Working | ⚠️ Partial | Android requires startFaceMonitoring() active |
| BiometricPrompt face unlock | N/A | ⚠️ Limited | Cannot force face unlock when both available |
Simulator/Emulator Behavior
iOS Simulator
What Works:
- ✅ Basic biometric authentication (
authenticateUser()) - UsesLocalAuthenticationframework - ✅
isBiometricAvailable()- Returns true if Face ID/Touch ID is enabled in simulator settings - ✅
getAvailableBiometrics()- Returns available biometric types - ✅
configureApiKey()- API key can be configured (but PUA SDK won't be used) - ✅
isApiKeyConfigured()- Returns true if API key is set
What Doesn't Work:
- ❌ PUA SDK Face Monitoring - PUA.framework is device-only (arm64), not available on simulator (x86_64/arm64 simulator)
- ❌ Continuous face detection -
startFaceMonitoring()will not detect faces - ❌ Real-time face counting - Face count callbacks won't be triggered
- ❌ Screen locking based on face detection - Not available
What Happens:
- When you call
startFaceMonitoring(), the plugin detects it's running on a simulator - It falls back to
LocalAuthenticationand basic platform APIs - The
onFaceCountChangedcallback may receive initial values (0 or 1) but won't update continuously - You'll see logs indicating "PUA SDK not available on simulator - using fallback"
To Test Face Monitoring:
- You must use a physical iOS device - PUA SDK requires device hardware
- Enable Face ID in device settings
- Grant camera permission when prompted
Android Emulator
What Works:
- ✅ Basic biometric authentication (
authenticateUser()) - Uses Android BiometricPrompt API - ✅
isBiometricAvailable()- Returns true if biometric hardware is emulated - ✅
getAvailableBiometrics()- Returns available biometric types - ✅
configureApiKey()- API key can be configured - ✅
isApiKeyConfigured()- Returns true if API key is set
What Doesn't Work:
- ❌ PUA SDK Face Monitoring - PUA SDK requires physical device with real camera
- ❌ Continuous face detection -
startFaceMonitoring()will not detect faces from camera - ❌ Real-time face counting - Face count callbacks won't be triggered
- ❌ Screen locking based on face detection - Not available
What Happens:
- When you call
startFaceMonitoring(), the plugin attempts to use PUA SDK - If PUA SDK fails (common on emulator), it may fall back to ML Kit face detection (if available)
- However, camera access on emulator is limited and may not work properly
- You'll see logs indicating "PUA SDK not available" or camera-related errors
To Test Face Monitoring:
- You must use a physical Android device - PUA SDK requires device hardware
- Device must have biometric hardware (fingerprint or face unlock)
- Grant camera permission when prompted
Real Device Behavior (iOS & Android)
What Works:
- ✅ Full PUA SDK Integration - All features work as intended
- ✅ Continuous Face Monitoring - Real-time face detection from camera
- ✅ Automatic Screen Locking - Locks when no face or multiple faces detected
- ✅ Biometric Authentication - Full Face ID/Touch ID/Fingerprint support
- ✅ Configurable Settings - Refresh rate, eyes-off-screen time, max faces
- ✅ Multiple Face Detection - Detects and reports multiple faces (exact count on iOS, estimated on Android)
Requirements:
- Physical device with camera
- Biometric hardware (Face ID, Touch ID, or Fingerprint)
- Camera permission granted
- Valid PUA API key configured
- Internet connection (for initial PUA SDK authentication)
Expected Behavior:
- Face monitoring starts immediately after
startFaceMonitoring()is called onFaceCountChangedcallback receives continuous updates:0when no face detected1when authorized user present>1when multiple faces detected
- Screen automatically locks/unlocks based on face detection state
- Biometric authentication prompts appear when needed
Installation
1. Add Dependencies
Add the following to your pubspec.yaml:
dependencies:
flutter_pua_auth: ^1.0.1
flutter_pua_auth_android: ^1.0.0
flutter_pua_auth_ios: ^1.0.0
⚠️ Important: You must add all three packages. The platform packages are required for native implementations.
2. Install Dependencies
flutter pub get
3. Platform-Specific Setup
⚠️ Important: After adding the package dependencies, you must configure the native iOS and Android projects to properly integrate the PUA SDK. These changes are required for the plugin to work correctly.
For detailed platform-specific setup instructions, see:
- Android Setup Guide - Complete Android configuration including AAR file setup, permissions, and dependencies
- iOS Setup Guide - Complete iOS configuration including framework setup, Podfile, and permissions
4. Import Platform Packages
In your app's main.dart, import the platform packages to trigger auto-registration:
import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure PUA API key
await FlutterPuaAuth.instance.configureApiKey("your-api-key-here");
runApp(MyApp());
}
Why This Is Required:
- The platform packages contain auto-registration code that runs when imported
- Without these imports, you'll get
MissingPluginExceptionerrors
Setup & Configuration
API Key Configuration
Why PUA API Key is Important
The PUA API key is essential for the plugin to function properly. Here's why:
-
SDK Authentication: The PUA SDK requires a valid API key to authenticate and initialize
- Without a valid API key, the PUA SDK will not start
- Face monitoring will fail with "PUA API key not configured" error
-
License Validation: The API key serves as a license to use the PUA SDK
- Each API key is tied to your application/account
- Invalid or expired keys will be rejected by the SDK
-
Security: The API key ensures only authorized applications can use the PUA SDK
- Protects The Whisper Company's intellectual property
- Prevents unauthorized usage
-
Feature Access: Valid API key enables all PUA SDK features:
- Continuous face monitoring
- Real-time face detection
- Advanced authentication capabilities
⚠️ Important:
- You must obtain a valid API key from The Whisper Company before using this plugin
- The API key must be configured before calling
startFaceMonitoring() - Without a valid API key, the plugin will fall back to basic platform biometric APIs (limited functionality)
How to Get PUA API Key
Contact The Whisper Company to obtain your PUA API key:
- Website: https://gitlab.com/the_whisper_company/twc.pua
- The API key is typically provided as part of your SDK license agreement
Configuration Methods
The PUA SDK requires an API key for authentication. You can configure it in two ways:
Method 1: Programmatic Configuration (Recommended)
Configure the API key in your app's main.dart:
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure PUA API key
const String puaApiKey = "your-api-key-here";
await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
runApp(MyApp());
}
Method 2: Android Resource File (Fallback)
For Android, you can also add the API key to android/app/src/main/res/values/pua_license.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pua_api_key" translatable="false">your-api-key-here</string>
</resources>
Priority Order:
- Programmatic configuration (Method 1) - takes precedence
- Android resource file (Method 2) - used as fallback if Method 1 not called
- Error - if neither is configured, face monitoring will fail
Note: iOS only supports Method 1 (programmatic configuration).
API Reference
FlutterPuaAuth
The main class for interacting with PUA authentication and face monitoring. Access via singleton: FlutterPuaAuth.instance.
Core Methods
configureApiKey(String apiKey)
Configures the PUA API key. Must be called before using any PUA SDK features.
isApiKeyConfigured()
Checks if the PUA API key has been configured. Returns Future<bool>.
authenticateUser({String? reason, bool allowCredentials = false})
Performs biometric authentication. Returns Future<bool>.
Platform Notes:
- iOS: Uses Face ID/Touch ID via LocalAuthentication
- Android: Uses BiometricPrompt (fingerprint/face unlock). If
startFaceMonitoring()is active, returns success immediately.
startFaceMonitoring({required Function(int) onFaceCountChanged, ...})
Starts continuous face monitoring from camera feed.
Parameters:
onFaceCountChanged(required): Callback with face count (0= no face,1= authorized,>1= multiple,-1= eyes off)refreshRate:'Instant','fast','medium', or'light'(default:'Instant')eyesOffScreenTime: 0.25-30.0 seconds (default: 10.0) - iOS only, Android useseyeClosedProbabilityThresholdnumberOfFacesAllowed: Minimum 1, no maximum (default: 1)lowLightThreshold: 200-2000 lux (default: 400.0)onLowLightWarning: Optional callback - iOS only, Android SDK doesn't support thisonError: Optional callback for PUA SDK errors
Face Count Values:
0: No face detected → Lock screen1: Authorized user → Unlock screen (auto re-authenticated)>1: Multiple faces → Lock if exceedsnumberOfFacesAllowed-1: Eyes off screen → Lock screen
stopFaceMonitoring()
Stops face monitoring and releases camera resources. Always call in dispose().
isBiometricAvailable()
Checks if biometric authentication is available. Returns Future<bool>.
getAvailableBiometrics()
Gets available biometric types: ["face"], ["fingerprint"], or ["face", "fingerprint"].
getPuaVersion()
Gets PUA SDK version. Returns Future<String> (e.g., "2.0"). iOS only - Android returns default "2.0".
inspectNativeSdk()
Inspects native SDK classes to see all available methods/fields. Logs to native console (logcat on Android, Xcode console on iOS). Useful for debugging SDK integration and understanding available methods.
Usage Examples
Basic Setup
In your app's main.dart, you must import the platform packages to trigger auto-registration:
import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
// ignore: unused_import
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
// ignore: unused_import
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure PUA API key
const String puaApiKey = "your-api-key-here";
await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
runApp(MyApp());
}
⚠️ Important: The platform package imports are required for the plugin to work. Without them, you'll get MissingPluginException errors. See Platform-Specific Setup above for links to detailed setup instructions.
Basic Authentication
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
class AuthScreen extends StatefulWidget {
@override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
bool _isAuthenticated = false;
Future<void> _authenticate() async {
final authenticated = await FlutterPuaAuth.instance.authenticateUser(
reason: 'Please authenticate to continue',
);
setState(() {
_isAuthenticated = authenticated;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _isAuthenticated
? Text('Authenticated!')
: ElevatedButton(
onPressed: _authenticate,
child: Text('Authenticate'),
),
),
);
}
}
Continuous Face Monitoring
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
class SecureScreen extends StatefulWidget {
@override
_SecureScreenState createState() => _SecureScreenState();
}
class _SecureScreenState extends State<SecureScreen> {
int _faceCount = 0;
bool _isLocked = true;
@override
void initState() {
super.initState();
_startMonitoring();
}
Future<void> _startMonitoring() async {
await FlutterPuaAuth.instance.startFaceMonitoring(
onFaceCountChanged: (count) {
setState(() {
_faceCount = count;
_isLocked = count != 1;
});
},
refreshRate: 'Instant',
eyesOffScreenTime: 2.0,
numberOfFacesAllowed: 1,
);
}
@override
void dispose() {
FlutterPuaAuth.instance.stopFaceMonitoring();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _isLocked
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.lock, size: 64),
Text('Screen Locked'),
Text('Face count: $_faceCount'),
],
),
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.lock_open, size: 64, color: Colors.green),
Text('Screen Unlocked'),
Text('Face count: $_faceCount'),
],
),
),
);
}
}
Complete Example with API Key Configuration
import 'package:flutter/material.dart';
// Import platform packages to trigger auto-registration
// ignore: unused_import
import 'package:flutter_pua_auth_android/flutter_pua_auth_android.dart';
// ignore: unused_import
import 'package:flutter_pua_auth_ios/flutter_pua_auth_ios.dart';
import 'package:flutter_pua_auth/flutter_pua_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure PUA API key
const String puaApiKey = "your-api-key-here";
try {
await FlutterPuaAuth.instance.configureApiKey(puaApiKey);
print('✅ PUA API key configured');
} catch (e) {
print('❌ Error configuring API key: $e');
}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _isMonitoring = false;
int _faceCount = 0;
String _status = 'Not monitoring';
Future<void> _checkApiKey() async {
final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
if (!isConfigured) {
setState(() {
_status = 'API key not configured';
});
}
}
Future<void> _startMonitoring() async {
final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
if (!isConfigured) {
_status = 'Please configure API key first';
return;
}
await FlutterPuaAuth.instance.startFaceMonitoring(
onFaceCountChanged: (count) {
setState(() {
_faceCount = count;
if (count == 0) {
_status = 'No face detected - Screen locked';
} else if (count == 1) {
_status = 'Face detected - Screen unlocked';
} else {
_status = '$count faces detected - Screen locked';
}
});
},
refreshRate: 'Instant',
eyesOffScreenTime: 2.0,
numberOfFacesAllowed: 1,
);
setState(() {
_isMonitoring = true;
_status = 'Monitoring started';
});
}
Future<void> _stopMonitoring() async {
await FlutterPuaAuth.instance.stopFaceMonitoring();
setState(() {
_isMonitoring = false;
_status = 'Monitoring stopped';
_faceCount = 0;
});
}
@override
void dispose() {
if (_isMonitoring) {
FlutterPuaAuth.instance.stopFaceMonitoring();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('PUA Auth Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Status: $_status', style: TextStyle(fontSize: 18)),
SizedBox(height: 20),
Text('Face Count: $_faceCount', style: TextStyle(fontSize: 24)),
SizedBox(height: 40),
ElevatedButton(
onPressed: _isMonitoring ? _stopMonitoring : _startMonitoring,
child: Text(_isMonitoring ? 'Stop Monitoring' : 'Start Monitoring'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _checkApiKey,
child: Text('Check API Key'),
),
],
),
),
);
}
}
Architecture
This plugin follows Flutter's federated plugin architecture:
Flutter App → FlutterPuaAuth → Platform Interface → Platform Implementation → Native PUA SDK
Communication:
- Method Channel: One-time operations (
configureApiKey,authenticateUser, etc.) - Event Channel: Continuous face count updates during monitoring
Platform Implementations:
- iOS:
flutter_pua_auth_ios→ PUA.framework (device-only) - Android:
flutter_pua_auth_android→ PUA AAR library
Simulator/Emulator Support
| Feature | iOS Simulator | Android Emulator | Real Device |
|---|---|---|---|
| API Key Configuration | ✅ | ✅ | ✅ |
| Biometric Auth UI | ✅ | ✅ | ✅ |
| Basic Authentication | ✅ (LocalAuth) | ✅ (BiometricPrompt) | ✅ (PUA SDK) |
| Face Monitoring | ❌ | ❌ | ✅ |
| Face Count Detection | ❌ | ❌ | ✅ |
| Screen Locking | ❌ | ❌ | ✅ |
Note: Face monitoring features require physical devices with camera and biometric hardware.
Important Notes
⚠️ API Key Configuration
Critical: The PUA API key is required for the plugin to work properly.
- Must be configured before use: Call
configureApiKey()inmain()before using any PUA SDK features - Required for face monitoring: Without a valid API key,
startFaceMonitoring()will fail - SDK Authentication: The API key authenticates your app with the PUA SDK servers
- License Validation: The API key validates your license to use the PUA SDK
- iOS: Only supports programmatic configuration
- Android: Supports both programmatic and resource file configuration (with fallback)
What Happens Without API Key:
- ❌ Face monitoring will not work
- ❌ PUA SDK will not initialize
- ❌ You'll see "PUA API key not configured" errors
- ⚠️ Plugin may fall back to basic platform APIs (limited functionality)
Always verify API key is configured:
final isConfigured = await FlutterPuaAuth.instance.isApiKeyConfigured();
if (!isConfigured) {
// Show error or configure API key
}
📱 Device Requirements
Real Device (Recommended)
- Physical Devices Only: PUA SDK requires physical devices with biometric hardware
- iOS: iPhone/iPad with Face ID or Touch ID
- Android: Device with fingerprint sensor or face unlock
- Camera: Front-facing camera required for face monitoring
- Internet: Required for initial PUA SDK authentication (one-time)
Why Physical Device?
- PUA SDK uses device-specific hardware features
- Camera access and face detection require real hardware
- Simulators/emulators don't have the necessary hardware capabilities
Simulator/Emulator (Limited Testing Only)
iOS Simulator:
- ✅ Can test basic biometric authentication UI
- ✅ Can test API key configuration
- ❌ Cannot test face monitoring - PUA SDK not available
- ❌ Cannot test continuous face detection
- Use for: UI testing, authentication flow testing
- Don't use for: Face monitoring, real security testing
Android Emulator:
- ✅ Can test basic biometric authentication UI
- ✅ Can test API key configuration
- ❌ Cannot test face monitoring - PUA SDK requires real camera
- ❌ Cannot test continuous face detection
- Use for: UI testing, authentication flow testing
- Don't use for: Face monitoring, real security testing
⚠️ Important: Always test face monitoring features on real devices before deploying to production.
🔒 Permissions
- Camera Permission: Required for face monitoring (requested automatically on Android, via Info.plist on iOS)
- Biometric Permission: Required for authentication (handled automatically by platform)
👥 Multiple Face Detection
- iOS: Provides exact face count (2, 3, 4, etc.) via
onMultipleFacesDetectedcallback - Android: Provides estimated count based on
lastKnownFaceCount + 1when multiple faces detected (SDK limitation)- The Android PUA SDK's
IntruderFaceDetectedcallback doesn't provide the exact face count - The plugin attempts to retrieve the actual count using reflection, but if unavailable, estimates it
- If
numberOfFacesAllowed = 2and 2 faces are detected, Android will report2(within limit, won't lock) - If
numberOfFacesAllowed = 2and 3+ faces are detected, Android will report3or more (exceeds limit, will lock)
- The Android PUA SDK's
Note:
numberOfFacesAllowedhas no maximum limit. You can set it to any value >= 1 (e.g., 5, 10, etc.)- The app will NOT lock when
faceCount <= numberOfFacesAllowed- this is handled in the Flutter BLoC logic - Use
inspectNativeSdk()to see what methods are available in the native SDK for face count detection
Lifecycle Management
Always call stopFaceMonitoring() in dispose() to prevent camera resource leaks.
Performance
- Use
'light'or'medium'refresh rate for better battery life - Face monitoring stops when app goes to background (platform limitation)
Troubleshooting
Common Issues
API Key Not Configured
- Ensure
configureApiKey()is called inmain()before use - Verify API key is valid
Face Monitoring Not Working
- Check API key:
await FlutterPuaAuth.instance.isApiKeyConfigured() - Verify camera permission granted
- Must run on physical device (not simulator/emulator)
- Check logs for PUA SDK errors
Build Errors
- iOS: Ensure
PUA.frameworkis present, runpod install - Android: Ensure all dependencies in
build.gradle, sync Gradle
Multiple Faces Not Detected (Android)
- Android SDK limitation:
IntruderFaceDetecteddoesn't provide exact count - Plugin estimates using
lastKnownFaceCount + 1 - Use
inspectNativeSdk()to see available methods
SDK Inspection Not Working
- Check logcat (Android):
adb logcat | grep "SDK_INSPECTION" - Check Xcode console (iOS): search for
[SDK_INSPECTION] - Ensure API key configured before inspection
License
Copyright © The Whisper Company. All rights reserved.
This plugin integrates with proprietary PUA SDKs from The Whisper Company. Please refer to The Whisper Company's licensing terms for SDK usage.
Demo App
See the example/ directory for a complete demo application showcasing:
- API key configuration
- Biometric authentication
- Continuous face monitoring
- Settings screen for configuration
- Screen locking/unlocking based on face detection