π― Learning Path:
Welcome to the REL-ID IDV Server Biometric Step-Up for Data Signing codelab! This tutorial shows you how to add high-security biometric authentication with liveness detection to your existing data signing implementation.
You'll enhance your existing data signing app with:
By completing this codelab, you'll master:
authLevel = 4 with authenticatorType = 1 for server biometricBefore starting, ensure you have:
Navigate to the relid-IDV-post-login-data-signing folder in your cloned repository:
cd relid-IDV-post-login-data-signing
Before implementing, let's understand what makes IDV Workflow 16 unique and why server biometric step-up requires special handling.
IDV Workflow 16 is the server biometric step-up authentication flow specifically designed for data signing operations. Unlike other IDV workflows, workflow 16:
authenticateUserAndSignData() is called with authLevel = 4 and authenticatorType = 1IDV Workflow | Scenario | When Used | Handler Location |
16 | Data Signing Step-Up Auth | High-security data signing | Provider-level (SDKEventProvider) |
9 | Notification Step-Up Auth | Sensitive notification actions | Screen-level (GetNotificationsScreen) |
10 | Biometric Opt-In | User enrolling in biometric | Standalone flow |
6 | Post-Login KYC | Know Your Customer verification | Standalone flow |
Unlike notifications (workflow 9) which use screen-level handlers, data signing uses a provider-level handler because:
User Submits Form (authLevel=4, authenticatorType=1) β
authenticateUserAndSignData(payload, 4, 1, reason) β
SDK Returns Sync Response β
SDK Triggers: getIDVSelfieProcessStartConfirmation (idvWorkflow: 16) β
[Global Handler] Navigate to IDVSelfieProcessStart β
User Starts Selfie Capture β
SDK Captures Selfie with Liveness Detection β
SDK Verifies Biometric on Server β
SDK Signs Data β
SDK Triggers: onAuthenticateUserAndSignData β
[Provider Handler] Close selfie screen if present β
[Provider Handler] Wait 300ms for transition β
[Provider Handler] Check errors (priority: error code β status code) β
[Provider Handler] Navigate to result screen or show error
Use authLevel = 4 with authenticatorType = 1 for:
Let's understand the technical architecture we'll implement for IDV workflow 16 in SDKEventProvider.tsx.
We'll modify SDKEventProvider.tsx to add three key pieces:
Why NavigationRef?
getRootState()import { navigationRef } from '../../tutorial/navigation/NavigationService';
// Check current route
const state = navigationRef.getRootState();
const currentRoute = state.routes[state.index];
if (currentRoute?.name === 'IDVSelfieProcessStart') {
navigationRef.goBack(); // Close screen
}
Priority 1: Check error.shortErrorCode !== 0
Priority 2: Check status.statusCode !== 100
// PRIORITY 1: Check error code first
if (response.error.shortErrorCode !== 0) {
Alert.alert('Failed', errorMessage);
return; // Stop processing
}
// PRIORITY 2: Check status code
if (response.status.statusCode !== 100) {
Alert.alert('Failed', statusMessage);
return; // Stop processing
}
// SUCCESS: Both checks passed
navigateToResults();
Why 300ms?
if (selfieScreenWasClosed) {
setTimeout(() => {
processDataSigningResult(response);
}, 300); // Wait for transition
return;
}
onAuthenticateUserAndSignData Event Triggered β
β
handleDataSigningResponse Called β
β
Check if IDVSelfieProcessStart screen is active β
β
If Active:
ββ Close screen with navigationRef.goBack()
ββ Wait 300ms for transition
ββ Process result with error priority checking
β
If Not Active:
ββ Process result immediately
β
processDataSigningResult:
ββ Check error.shortErrorCode !== 0 (Priority 1)
ββ Check status.statusCode !== 100 (Priority 2)
ββ If success: Navigate to result screen
In this codelab, you'll modify only one file:
File | Purpose | Changes |
| Central event handler | Add navigationRef, error priority processing, selfie auto-closure |
All other files (screens, services, types) remain unchanged from the base data signing codelab.
Let's start by importing the navigationRef that gives us direct access to the navigation state.
Open src/uniken/providers/SDKEventProvider.tsx and locate the imports section at the top of the file.
Find this line (around line 24):
import NavigationService from '../../tutorial/navigation/NavigationService';
Replace with:
import NavigationService, { navigationRef } from '../../tutorial/navigation/NavigationService';
The navigationRef export from NavigationService.ts provides:
Your NavigationService.ts should already have this export from the MFA codelab:
// src/tutorial/navigation/NavigationService.ts
export const navigationRef = createNavigationContainerRef<RootStackParamList>();
Now that we have navigationRef imported, we'll use it to:
navigationRef.goBack()Let's move to the next step to add error priority processing.
Now let's add a dedicated function to process data signing results with proper error priority checking.
In SDKEventProvider.tsx, locate the handleDataSigningResponse function (around line 183 in the previous implementation).
Add this new function before handleDataSigningResponse:
// Line 133 - Process result with error priority checking
const processDataSigningResult = useCallback((response: RDNADataSigningResponse) => {
// Convert SDK response to internal format
const internalResponse: DataSigningResponse = {
dataPayload: response.dataPayload,
dataPayloadLength: response.dataPayloadLength,
reason: response.reason,
payloadSignature: response.payloadSignature,
dataSignatureID: response.dataSignatureID,
authLevel: response.authLevel,
authenticationType: response.authenticationType,
status: response.status,
error: response.error,
};
// Update state
setSigningResult(internalResponse);
setFormState(prev => ({ ...prev, isLoading: false }));
setPasswordModalState(prev => ({ ...prev, isVisible: false }));
// PRIORITY 1: Check error code first
if (response.error.shortErrorCode !== 0) {
const errorMessage = response.error.errorString ||
DataSigningService.getErrorMessage(response.error.shortErrorCode);
Alert.alert('Data Signing Failed', errorMessage);
return; // Stop processing
}
// PRIORITY 2: Check status code
if (response.status.statusCode !== 100) {
const statusMessage = response.status.statusMessage || 'Operation failed with an unknown error';
Alert.alert('Data Signing Failed', statusMessage);
return; // Stop processing
}
// SUCCESS: Both checks passed
const displayData = DataSigningService.formatSigningResultForDisplay(internalResponse);
setResultDisplay(displayData);
NavigationService.navigate('DataSigningResult');
}, []);
Priority 1 - Error Code Check:
if (response.error.shortErrorCode !== 0) {
// Authentication failed, biometric mismatch, user cancelled, etc.
Alert.alert('Data Signing Failed', errorMessage);
return; // Stop - don't check status code
}
Priority 2 - Status Code Check:
if (response.status.statusCode !== 100) {
// Operation failed at SDK level
Alert.alert('Data Signing Failed', statusMessage);
return; // Stop - don't navigate
}
Success Path:
// Both checks passed - safe to show results
const displayData = DataSigningService.formatSigningResultForDisplay(internalResponse);
setResultDisplay(displayData);
NavigationService.navigate('DataSigningResult');
DataSigningService.getErrorMessage() for readable errorsNow we have error priority processing ready. In the next step, we'll implement the main handler that:
processDataSigningResult functionNow let's implement the main handler that detects and closes the selfie screen before processing results.
In SDKEventProvider.tsx, replace your existing
handleDataSigningResponse
function with this enhanced version:
// Line 183 - Main handler with selfie screen auto-closure
const handleDataSigningResponse = useCallback((response: RDNADataSigningResponse) => {
console.log('SDKEventProvider - Data signing response received:', response);
// Check if selfie screen is active and close it
const closeSelfieScreenIfPresent = () => {
console.log('SDKEventProvider - Checking if selfie screen needs to be closed for data signing');
try {
// Check if navigation is ready
if (!navigationRef.isReady()) {
console.warn('SDKEventProvider - Navigation not ready');
return false;
}
// Get current navigation state
const state = navigationRef.getRootState();
const currentRoute = state.routes[state.index];
// Check if IDVSelfieProcessStart screen is on top
if (currentRoute?.name === 'IDVSelfieProcessStart') {
console.log('SDKEventProvider - IDVSelfieProcessStart screen detected for data signing, closing it');
navigationRef.goBack();
console.log('SDKEventProvider - Selfie screen closed');
return true;
} else {
console.log('SDKEventProvider - No IDVSelfieProcessStart screen on top, current route:', currentRoute?.name);
}
} catch (error) {
console.error('SDKEventProvider - Error checking/closing selfie screen:', error);
}
return false;
};
// Execute screen closure detection
const selfieScreenWasClosed = closeSelfieScreenIfPresent();
// If screen was closed, delay processing for smooth transition
if (selfieScreenWasClosed) {
console.log('SDKEventProvider - Delaying result processing for screen transition (300ms)');
setTimeout(() => {
console.log('SDKEventProvider - Processing data signing result after screen close');
processDataSigningResult(response);
}, 300);
return;
}
// No selfie screen present, process immediately
console.log('SDKEventProvider - Processing data signing result immediately');
processDataSigningResult(response);
}, [processDataSigningResult]);
const closeSelfieScreenIfPresent = () => {
// Check if navigation is ready
if (!navigationRef.isReady()) {
return false; // Not ready, can't detect screen
}
// Get current route from navigation state
const state = navigationRef.getRootState();
const currentRoute = state.routes[state.index];
// Detect IDVSelfieProcessStart screen
if (currentRoute?.name === 'IDVSelfieProcessStart') {
navigationRef.goBack(); // Close the screen
return true; // Screen was closed
}
return false; // No selfie screen present
};
Key Points:
navigationRef.isReady(): Ensures navigation system is initializednavigationRef.getRootState(): Gets current navigation hierarchystate.routes[state.index]: Gets the active route on top of stacknavigationRef.goBack(): Closes the screen (same as back button press)const selfieScreenWasClosed = closeSelfieScreenIfPresent();
if (selfieScreenWasClosed) {
// Screen was closed - add delay for smooth transition
setTimeout(() => {
processDataSigningResult(response);
}, 300);
return;
}
// No screen to close - process immediately
processDataSigningResult(response);
Why This Pattern?:
1. onAuthenticateUserAndSignData event fires
β
2. handleDataSigningResponse called with response
β
3. closeSelfieScreenIfPresent() executes:
ββ Check navigationRef.isReady() β β
ββ Get current route β 'IDVSelfieProcessStart'
ββ Call navigationRef.goBack() β Screen closes
ββ Return true
β
4. selfieScreenWasClosed === true
β
5. setTimeout 300ms
β
6. processDataSigningResult(response):
ββ Check error.shortErrorCode !== 0 β β (0 = success)
ββ Check status.statusCode !== 100 β β (100 = success)
ββ Navigate to DataSigningResultScreen
β
7. User sees results on clean screen
Scenario 1: Server Biometric Success
Scenario 2: Server Biometric Error
Scenario 3: Password/LDA Success
Scenario 4: Password/LDA Error
The implementation handles:
Your implementation is complete! Let's test it to verify everything works correctly.
Let's verify your IDV workflow 16 implementation works correctly across all scenarios.
Expected Logs:
SDKEventProvider - Data signing response received
SDKEventProvider - Checking if selfie screen needs to be closed for data signing
SDKEventProvider - IDVSelfieProcessStart screen detected for data signing, closing it
SDKEventProvider - Selfie screen closed
SDKEventProvider - Delaying result processing for screen transition (300ms)
SDKEventProvider - Processing data signing result after screen close
Expected Error Messages:
Expected Logs:
SDKEventProvider - Data signing response received
SDKEventProvider - Checking if selfie screen needs to be closed for data signing
SDKEventProvider - No IDVSelfieProcessStart screen on top, current route: DataSigningInput
SDKEventProvider - Processing data signing result immediately
Core Functionality:
Error Handling:
Other Authentication Methods:
Edge Cases:
Issue: Selfie screen doesn't close
navigationRef is imported correctlyIssue: Alert appears behind selfie screen
Issue: Results appear for error responses
Issue: Password/LDA delayed unnecessarily
You've successfully added IDV Server Biometric Step-Up Authentication (Workflow 16) to your data signing implementation!
authLevel = 4 with server biometricNavigationRef for Cross-Hierarchy Management:
import { navigationRef } from '../../tutorial/navigation/NavigationService';
const state = navigationRef.getRootState();
const currentRoute = state.routes[state.index];
if (currentRoute?.name === 'IDVSelfieProcessStart') {
navigationRef.goBack();
}
Error Priority Checking:
// Priority 1: Error code
if (response.error.shortErrorCode !== 0) {
Alert.alert('Failed', errorMessage);
return;
}
// Priority 2: Status code
if (response.status.statusCode !== 100) {
Alert.alert('Failed', statusMessage);
return;
}
Conditional Delay for Smooth Transitions:
if (selfieScreenWasClosed) {
setTimeout(() => {
processDataSigningResult(response);
}, 300);
return;
}
processDataSigningResult(response);
Before deploying, ensure:
Explore More REL-ID Features:
Enhance Your Implementation:
π Congratulations! You've mastered IDV Server Biometric Step-Up Authentication for Data Signing with REL-ID SDK!
Your app now provides high-security biometric authentication with liveness detection for sensitive data signing operations, with a smooth, professional user experience!