🎯 Learning Path:
Welcome to the REL-ID IDV Post-Login Server Biometric Authentication Flow codelab! This tutorial teaches you how to implement secure server-side biometric verification using stored biometric templates with liveness detection for activated customers.
In this codelab, you'll implement a complete post-login server biometric authentication workflow:
This codelab demonstrates post-login server biometric authentication, which is different from KYC:
KYC Flow (Workflow 6, Challenge Mode 9):
Document Scan → Selfie Capture → Biometric Consent → Complete KYC
(Creates new biometric template)
Server Biometric Auth Flow (Workflow 8, Challenge Mode 25 - This Codelab):
Template Check → Start Authentication → Selfie Capture → View Result
(Uses existing biometric template for verification)
Critical Differences:
onIDVServerBiometricAuthenticationResult instead of onActivatedCustomerKYCResponseUnderstanding the complete flow is crucial before implementation:
1. User Login (Activated Customer with Biometric Template)
↓ (Existing credentials authenticated)
2. Dashboard → Open Drawer Menu
↓ (User navigates to Biometric Authentication option)
3. 🆕 Server Biometric Authentication Screen
├─ Screen loaded → onContentLoaded() triggers
├─ Automatic template status check
├─ checkIDVUserBiometricTemplateStatus() API called
└─ Wait for onIDVCheckUserBiometricTemplateStatus event
↓
4. Template Status Validation
├─ onIDVCheckUserBiometricTemplateStatus event received
├─ Screen-level handler processes event
├─ Parse status code:
│ ├─ statusCode === 100: Template exists → Enable button
│ └─ statusCode === 600: No template → Disable button, show message
└─ Display template status on screen
↓
5. Start Authentication (only if template exists)
├─ User clicks "Start Authentication" button
├─ initiateIDVServerBiometricAuthentication("Server Biometric Authentication") API called
└─ SDK initiates server biometric authentication workflow
↓
6. 📸 Selfie Capture Process (Reused from IDV MFA Flow)
├─ getIDVSelfieProcessStartConfirmation event
│ • idvWorkFlow: 8 (selfie capture)
├─ Navigate to IDVSelfieProcessStartConfirmationScreen
├─ User clicks "Start Selfie Capture" OR Close button (X)
│ ├─ "Start Selfie Capture": setIDVSelfieProcessStartConfirmation(true, 8) → Opens camera
│ └─ Close button: setIDVSelfieProcessStartConfirmation(false, 8) → Triggers result event with error 240
└─ Native Selfie Capture (Aware FaceCapture) with liveness detection
↓
7. ✅ Server Biometric Authentication Result
├─ onIDVServerBiometricAuthenticationResult event triggered
│ • challengeMode: 25 (server biometric authentication)
├─ Screen-level handler receives event (even if on different screen)
├─ Navigate back to ServerBiometricAuthentication screen
│ • NavigationService.navigate('ServerBiometricAuthentication')
├─ 300ms delay for navigation to complete
├─ Parse idvResponse for audit data:
│ ├─ Authentication Status (SUCCESS/FAILURE from idv_audit_info.status)
│ ├─ Orchestration Use Case (RDNA_IDV_POSTLOGIN_IDV_SELFIE_BIOMETRIC)
│ ├─ Transaction ID (idv_audit_transaction_id)
│ └─ Attempts Left (attempts_left)
├─ Display result modal with audit data
└─ User clicks "Back to Dashboard" or "OK"
↓
8. Completion
├─ Navigate to Dashboard or stay on screen
└─ User can perform another authentication (template check happens on load)
The biometric authentication flow is accessed from the drawer menu:

The drawer menu includes a new "🔐 Biometric Authentication" menu item that navigates to the ServerBiometricAuthenticationScreen.
For post-login server biometric authentication, you'll use these specific values:
Parameter | Value | Description |
idvWorkflow |
| Selfie capture for server biometric authentication |
challengeMode |
| Server biometric authentication result event |
Status Codes |
| Template exists / No template |
Comparison with Other IDV Flows:
Aspect | KYC Flow | Additional Document Scan | Server Biometric Auth |
Workflow | 6 | 7 | 8 |
Challenge Mode | 9 | 10 | 25 |
Template Check | No | No | Yes (required) |
Document Scan | Yes | Yes | No |
Selfie Capture | Yes | No | Yes |
Biometric Consent | Yes | No | No |
Template Creation | Yes (new) | No | No (uses existing) |
Entry Point | ActivatedCustomerKYCScreen | AdditionalDocumentScanScreen | ServerBiometricAuthenticationScreen |
API | initiateActivatedCustomerKYC | initiateIDVAdditionalDocumentScan | initiateIDVServerBiometricAuthentication and checkIDVUserBiometricTemplateStatus |
Result Event | onActivatedCustomerKYCResponse | onIDVAdditionalDocumentScanResult | onIDVServerBiometricAuthenticationResult |
Ensure you have completed these requirements:
# Required tools
Node.js >= 16
Cordova CLI
Xcode (for iOS) or Android Studio (for Android)
The code to get started can be found in a GitHub repository.
You can clone the repository using the following command:
git clone https://github.com/uniken-team/relid-codelab-cordova.git
Navigate to the relid-IDV-post-login-server-biometric-authentication folder:
cd relid-codelab-cordova/relid-IDV-post-login-server-biometric-authentication
The post-login server biometric authentication codelab follows this structure:
relid-IDV-post-login-server-biometric-authentication/
├── 📱 Cordova Single Page Application Architecture
│ ├── platforms/ # Platform-specific builds (iOS, Android)
│ ├── plugins/ # Cordova plugins
│ │ ├── cordova-plugin-file/ # File system access plugin
│ │ └── cordova-plugin-rdna/ # REL-ID Native Bridge with Aware FaceCapture
│ ├── hooks/ # Build hooks
│ ├── RdnaClient/ # 🆕 Local Cordova Plugin
│ └── www/ # 🆕 SPA Application Root (see below)
├── 📦 SPA Application Structure (www/)
│ ├── index.html # ⚠️ SINGLE HTML FILE with all templates
│ ├── css/
│ │ └── index.css # Styles for all screens
│ ├── js/
│ │ └── app.js # App initialization (deviceready, AppInitializer)
│ └── src/
│ ├── tutorial/ # Enhanced MFA + IDV flow
│ │ ├── navigation/ # Enhanced navigation with IDV support
│ │ │ └── NavigationService.js # SPA navigation (template swapping)
│ │ └── screens/ # Enhanced screens with IDV
│ │ ├── mfa/ # 🔐 MFA screens (base authentication)
│ │ │ ├── CheckUserScreen.js # User validation
│ │ │ ├── ActivationCodeScreen.js # OTP verification
│ │ │ ├── SetPasswordScreen.js # Password creation
│ │ │ ├── VerifyPasswordScreen.js # Password verification
│ │ │ ├── DashboardScreen.js # Main dashboard
│ │ │ └── ... # Other MFA screens
│ │ └── idv/ # 🆕 IDV Post-Login Server Biometric Authentication screens
│ │ ├── serverBiometricAuthentication/ # 🆕 Post-Login Server Biometric Auth
│ │ │ └── ServerBiometricAuthenticationScreen.js # Template check + authentication
│ │ └── IDVSelfieProcessStartConfirmationScreen.js # Selfie capture initiation (workflow 8)
│ └── uniken/ # 🛡️ Enhanced REL-ID Integration
│ ├── providers/ # Enhanced providers
│ │ ├── SDKEventProvider.js # Complete MFA event handling
│ │ └── idv/ # 🆕 IDV event providers
│ │ └── SDKIDVEventProvider.js # Complete IDV event handling
│ ├── services/ # 🆕 Enhanced SDK service layer
│ │ ├── rdnaService.js # Base MFA service layer
│ │ └── idv/ # 🆕 IDV service layer
│ │ ├── rdnaIDVService.js # Complete IDV API methods
│ │ │ # - 🆕 checkIDVUserBiometricTemplateStatus()
│ │ │ # - 🆕 initiateIDVServerBiometricAuthentication()
│ │ │ # - setIDVSelfieProcessStartConfirmation()
│ │ └── rdnaIDVEventManager.js # Complete IDV event management
│ │ # - Event listener registration
│ │ # - Event handler setters
│ │ # - 🆕 Template status check event handling
│ │ # - 🆕 Server biometric auth result event handling
│ ├── utils/ # Utility functions
│ │ └── connectionProfileParser.js # File loading with cordova-plugin-file
│ ├── MTDContext/ # MTD threat management
│ └── SessionContext/ # Session management
└── 📚 Production Configuration
├── config.xml # Cordova configuration
└── package.json # Dependencies
The project uses an automated Cordova hook for IDV asset management, but server biometric authentication does not require document scanning assets. Unlike document scanning flows (Additional Document Scan, KYC), this flow only needs the Aware FaceCapture library for biometric verification.
Document Scanner Assets (Not Required):
regula.license - Document reader license (only for document scanning)db.dat - 110.5MB Regula document recognition database (only for document scanning)Certificates.bundle/ - ICAO PKD certificates (only for document scanning)idv-native-resources/ folder - Not needed for biometric authenticationKey Difference: This codelab demonstrates post-login server biometric authentication which only uses selfie capture with liveness detection. It does NOT scan identity documents, so document scanner assets are not required.
Biometric Capture Library (Pre-configured):
cordova-plugin-rdna plugin If you're building a document scanning flow (like KYC or Additional Document Scan), you'll need the automated hook structure:
your-cordova-project/
├── idv-native-resources/ # Only needed for document scanning flows
│ ├── common/ # Shared files (regula.license, db.dat)
│ ├── ios/ # iOS certificates
│ └── android/ # Android certificates
├── hooks/
│ └── after_prepare/
│ └── copy_idv_native_resources.js # Automated hook script
Hook Script: hooks/after_prepare/copy_idv_native_resources.js
When you run cordova prepare or cordova build, this hook automatically copies assets to platform-specific directories.
For This Server Biometric Authentication Codelab:
idv-native-resources/ folder is optional (can be empty or omitted)This project includes a local Cordova plugin. Ensure the plugin directory exists in your project root:
# Verify plugin directory exists
ls -la ./RdnaClient
The SDK plugin is included as a local plugin in the project. Install it from the local directory:
cordova plugin add ./RdnaClient
# Add platforms
cordova platform add ios
cordova platform add android
# Prepare platforms
cordova prepare
Follow the Cordova platform setup guide for platform-specific configuration.
Before implementing the authentication screen, you need to add two API methods to the IDV service layer:
checkIDVUserBiometricTemplateStatus() - Check if user has biometric template on serverinitiateIDVServerBiometricAuthentication(reason) - Start authentication workflowAdd the following method to rdnaIDVService.js:
// www/src/uniken/services/idv/rdnaIDVService.js
/**
* Check IDV User Biometric Template Status
*
* Checks if the user has a biometric template stored on the server.
* This should be called before allowing server biometric authentication.
*
* @returns {Promise} Promise that resolves with sync response on successful check initiation
* @throws {Promise} Promise that rejects with sync response on failure
*
* Flow:
* 1. SDK checks template availability on server
* 2. SDK triggers onIDVCheckUserBiometricTemplateStatus event with status code:
* - 100 = Template present (user can authenticate)
* - 600 = Template does not exist (user must complete biometric opt-in first)
* - 400/500 = Error
*
* Success Response:
* - error.longErrorCode = 0
* - Wait for onIDVCheckUserBiometricTemplateStatus event
*
* Error Responses:
* - Sync error: error.longErrorCode !== 0
*/
async checkIDVUserBiometricTemplateStatus() {
return new Promise((resolve, reject) => {
console.log('RdnaIDVService - Checking user biometric template status');
com.uniken.rdnaplugin.RdnaClient.checkIDVUserBiometricTemplateStatus(
(response) => {
console.log('RdnaIDVService - checkIDVUserBiometricTemplateStatus sync callback received');
const result = JSON.parse(response);
if (result.error && result.error.longErrorCode === 0) {
console.log('RdnaIDVService - checkIDVUserBiometricTemplateStatus sync response success');
console.log('RdnaIDVService - Waiting for onIDVCheckUserBiometricTemplateStatus event');
resolve(result);
} else {
console.error('RdnaIDVService - checkIDVUserBiometricTemplateStatus sync response error:', result);
reject(result);
}
},
(error) => {
console.error('RdnaIDVService - checkIDVUserBiometricTemplateStatus error:', error);
const result = JSON.parse(error);
reject(result);
},
[] // No parameters for this API
);
});
}
Add the following method to rdnaIDVService.js:
/**
* Initiate IDV Server Biometric Authentication
*
* Initiates post-login server biometric authentication workflow for already activated
* customers with stored biometric templates.
*
* @param {string} reason - Reason for authentication (e.g., "Server Biometric Authentication")
* @returns {Promise} Promise that resolves with sync response on successful initiation
* @throws {Promise} Promise that rejects with sync response on failure
*
* Flow:
* 1. SDK initiates server biometric authentication workflow
* - workflow: 8 (selfie capture)
* - challengeMode: 25 (server biometric auth result)
* 2. SDK automatically triggers:
* - getIDVSelfieProcessStartConfirmation event (idvWorkFlow: 8)
* 3. After selfie capture, SDK triggers:
* - onIDVServerBiometricAuthenticationResult event (challengeMode: 25)
*
* Success Response:
* - error.longErrorCode = 0
*
* Error Responses:
* - Sync error: error.longErrorCode !== 0
* - Backend error: Received in onIDVServerBiometricAuthenticationResult event
*/
async initiateIDVServerBiometricAuthentication(reason) {
return new Promise((resolve, reject) => {
console.log('RdnaIDVService - Initiating IDV server biometric authentication');
console.log('RdnaIDVService - Reason:', reason);
com.uniken.rdnaplugin.RdnaClient.initiateIDVServerBiometricAuthentication(
(response) => {
console.log('RdnaIDVService - initiateIDVServerBiometricAuthentication sync callback received');
const result = JSON.parse(response);
if (result.error && result.error.longErrorCode === 0) {
console.log('RdnaIDVService - initiateIDVServerBiometricAuthentication sync response success');
console.log('RdnaIDVService - Waiting for onIDVServerBiometricAuthenticationResult event');
resolve(result);
} else {
console.error('RdnaIDVService - initiateIDVServerBiometricAuthentication sync response error:', result);
reject(result);
}
},
(error) => {
console.error('RdnaIDVService - initiateIDVServerBiometricAuthentication error:', error);
const result = JSON.parse(error);
reject(result);
},
[reason] // Parameters array
);
});
}
JSON.parse()Implement event listeners and handlers for template status check and authentication result events in the IDV Event Manager.
Add the following to rdnaIDVEventManager.js:
// www/src/uniken/services/idv/rdnaIDVEventManager.js
const RdnaIDVEventManager = {
_initialized: false,
checkUserBiometricTemplateStatusHandler: null,
serverBiometricAuthenticationResultHandler: null,
/**
* Initialize event manager and register all IDV events
*/
init() {
if (this._initialized) {
console.log('RdnaIDVEventManager - Already initialized');
return;
}
console.log('RdnaIDVEventManager - Initializing');
this.registerEventListeners();
this._initialized = true;
},
/**
* Register all IDV event listeners
*/
registerEventListeners() {
console.log('RdnaIDVEventManager - Registering event listeners');
// Check User Biometric Template Status Event
document.addEventListener(
'onIDVCheckUserBiometricTemplateStatus',
this.onCheckUserBiometricTemplateStatus.bind(this),
false
);
// Server Biometric Authentication Result Event
document.addEventListener(
'onIDVServerBiometricAuthenticationResult',
this.onServerBiometricAuthenticationResult.bind(this),
false
);
},
/**
* Template Status Check Event Handler
* Triggered after checkIDVUserBiometricTemplateStatus API call
*/
onCheckUserBiometricTemplateStatus(event) {
console.log('RdnaIDVEventManager - Check user biometric template status event received');
try {
// Handle multiple event object formats
const response = event.response || event.detail || event;
const data = typeof response === 'string' ? JSON.parse(response) : response;
console.log('RdnaIDVEventManager - User ID:', data.userID);
console.log('RdnaIDVEventManager - Attempts Left:', data.attemptsLeft);
console.log('RdnaIDVEventManager - Challenge Mode:', data.challengeMode);
console.log('RdnaIDVEventManager - Status Code:', data.status?.statusCode);
console.log('RdnaIDVEventManager - Status Message:', data.status?.statusMessage);
console.log('RdnaIDVEventManager - Error Code:', data.error?.longErrorCode);
console.log('RdnaIDVEventManager - Error String:', data.error?.errorString);
console.log('RdnaIDVEventManager - IDV Response:', data.idvResponse);
if (this.checkUserBiometricTemplateStatusHandler) {
this.checkUserBiometricTemplateStatusHandler(data);
} else {
console.warn('RdnaIDVEventManager - No handler registered for check user biometric template status');
}
} catch (error) {
console.error('RdnaIDVEventManager - Failed to parse check user biometric template status:', error);
}
},
/**
* Server Biometric Authentication Result Event Handler
* Triggered after selfie capture completes during server biometric authentication
* Challenge Mode: 25
*/
onServerBiometricAuthenticationResult(event) {
console.log('RdnaIDVEventManager - Server biometric authentication result event received');
try {
// Handle multiple event object formats
const response = event.response || event.detail || event;
const data = typeof response === 'string' ? JSON.parse(response) : response;
console.log('RdnaIDVEventManager - User ID:', data.userID);
console.log('RdnaIDVEventManager - Attempts Left:', data.attemptsLeft);
console.log('RdnaIDVEventManager - Challenge Mode:', data.challengeMode);
console.log('RdnaIDVEventManager - Status Code:', data.status?.statusCode);
console.log('RdnaIDVEventManager - Status Message:', data.status?.statusMessage);
console.log('RdnaIDVEventManager - Error Code:', data.error?.longErrorCode);
console.log('RdnaIDVEventManager - Error String:', data.error?.errorString);
console.log('RdnaIDVEventManager - IDV Response:', data.idvResponse);
if (this.serverBiometricAuthenticationResultHandler) {
this.serverBiometricAuthenticationResultHandler(data);
} else {
console.warn('RdnaIDVEventManager - No handler registered for server biometric authentication result');
}
} catch (error) {
console.error('RdnaIDVEventManager - Failed to parse server biometric authentication result:', error);
}
},
/**
* Set handler for template status check event
* @param {Function|null} handler - Handler function or null to unregister
*/
setCheckUserBiometricTemplateStatusHandler(handler) {
console.log('RdnaIDVEventManager - Setting check user biometric template status handler');
this.checkUserBiometricTemplateStatusHandler = handler;
},
/**
* Set handler for server biometric authentication result event
* @param {Function|null} handler - Handler function or null to unregister
*/
setServerBiometricAuthenticationResultHandler(handler) {
console.log('RdnaIDVEventManager - Setting server biometric authentication result handler');
this.serverBiometricAuthenticationResultHandler = handler;
},
/**
* Cleanup event manager
*/
cleanup() {
console.log('RdnaIDVEventManager - Cleaning up');
// Remove event listeners
document.removeEventListener(
'onIDVCheckUserBiometricTemplateStatus',
this.onCheckUserBiometricTemplateStatus.bind(this),
false
);
document.removeEventListener(
'onIDVServerBiometricAuthenticationResult',
this.onServerBiometricAuthenticationResult.bind(this),
false
);
// Clear handlers
this.checkUserBiometricTemplateStatusHandler = null;
this.serverBiometricAuthenticationResultHandler = null;
this._initialized = false;
}
};
document.addEventListener() for Cordova eventsevent.response, event.detail, event)Implement the main screen that handles template status check, authentication initiation, and result display. This screen uses screen-level event handlers for robust event handling.
Create ServerBiometricAuthenticationScreen.js:
// www/src/tutorial/screens/idv/serverBiometricAuthentication/ServerBiometricAuthenticationScreen.js
const ServerBiometricAuthenticationScreen = {
// Local state object (replaces React useState)
state: {
isCheckingStatus: false,
isAuthenticating: false,
templateStatus: {
checked: false,
exists: false,
statusCode: 0,
statusMessage: ''
},
showModal: false,
modalTitle: '',
modalMessage: '',
isSuccess: false
},
/**
* Lifecycle hook called when screen content is loaded
*/
onContentLoaded(params) {
console.log('ServerBiometricAuthenticationScreen - Content loaded with params:',
JSON.stringify(params, null, 2));
// Reset state for fresh start
this.state = {
isCheckingStatus: false,
isAuthenticating: false,
templateStatus: {
checked: false,
exists: false,
statusCode: 0,
statusMessage: ''
},
showModal: false,
modalTitle: '',
modalMessage: '',
isSuccess: false
};
// Setup event listeners
this.setupEventListeners();
// Hide any existing modal
this.hideModal();
// Check template status on load
this.checkTemplateStatus();
},
/**
* Setup event listeners
* Registers screen-level SDK event handlers that persist across navigation
*/
setupEventListeners() {
console.log('ServerBiometricAuthenticationScreen - Setting up event listeners');
// Button click handlers
const menuBtn = document.getElementById('server-biometric-menu-button');
if (menuBtn) {
menuBtn.onclick = () => {
NavigationService.openDrawer();
};
}
const startBtn = document.getElementById('start-authentication-button');
if (startBtn) {
startBtn.onclick = () => this.handleStartAuthentication();
}
const modalOkBtn = document.getElementById('server-biometric-modal-ok-btn');
if (modalOkBtn) {
modalOkBtn.onclick = () => this.handleModalOK();
}
const modalDashboardBtn = document.getElementById('server-biometric-modal-dashboard-btn');
if (modalDashboardBtn) {
modalDashboardBtn.onclick = () => this.handleModalDashboard();
}
// Screen-level SDK event handlers (persist across navigation)
const eventManager = rdnaIDVService.getEventManager();
eventManager.setCheckUserBiometricTemplateStatusHandler(
this.handleCheckUserBiometricTemplateStatusResponse.bind(this)
);
eventManager.setServerBiometricAuthenticationResultHandler(
this.handleServerBiometricAuthenticationResultResponse.bind(this)
);
},
/**
* Checks if user biometric template exists on server
*/
async checkTemplateStatus() {
console.log('ServerBiometricAuthenticationScreen - Checking biometric template status');
this.setCheckingStatus(true);
try {
const syncResponse = await rdnaIDVService.checkIDVUserBiometricTemplateStatus();
console.log('ServerBiometricAuthenticationScreen - checkIDVUserBiometricTemplateStatus sync response successful');
// The actual result will come via onIDVCheckUserBiometricTemplateStatus event
} catch (error) {
console.error('ServerBiometricAuthenticationScreen - checkIDVUserBiometricTemplateStatus sync error:', error);
const errorMessage = error.error?.errorString || 'Unknown error';
this.state.templateStatus = {
checked: true,
exists: false,
statusCode: 0,
statusMessage: `Error: ${errorMessage}`
};
this.updateTemplateStatusUI();
this.setCheckingStatus(false);
alert(`Template Status Check Error\n\nFailed to check template status: ${errorMessage}`);
}
},
/**
* Handles check user biometric template status response event
* Status Codes:
* - 100 = Template present
* - 600 = Template does not exist
* - 400/500 = Error
*/
handleCheckUserBiometricTemplateStatusResponse(data) {
console.log('ServerBiometricAuthenticationScreen - Check user biometric template status response received');
const errorCode = data.error?.longErrorCode;
const statusCode = data.status?.statusCode;
const statusMessage = data.status?.statusMessage || 'Unknown status';
// First check: error code
if (errorCode !== 0) {
this.state.templateStatus = {
checked: true,
exists: false,
statusCode: statusCode || 0,
statusMessage: `Error: ${data.error?.errorString}`
};
this.updateTemplateStatusUI();
this.setCheckingStatus(false);
return;
}
// Second check: status code
if (statusCode === 100) {
// Template present
this.state.templateStatus = {
checked: true,
exists: true,
statusCode: statusCode,
statusMessage: statusMessage
};
} else if (statusCode === 600) {
// Template does not exist
this.state.templateStatus = {
checked: true,
exists: false,
statusCode: statusCode,
statusMessage: statusMessage
};
} else {
// Other status codes (400/500 = error)
this.state.templateStatus = {
checked: true,
exists: false,
statusCode: statusCode || 0,
statusMessage: statusMessage
};
}
this.updateTemplateStatusUI();
this.setCheckingStatus(false);
},
/**
* Initiates server biometric authentication workflow
*/
async handleStartAuthentication() {
console.log('ServerBiometricAuthenticationScreen - Starting server biometric authentication');
this.setAuthenticating(true);
try {
const reason = 'Server Biometric Authentication';
console.log('ServerBiometricAuthenticationScreen - Calling initiateIDVServerBiometricAuthentication');
const syncResponse = await rdnaIDVService.initiateIDVServerBiometricAuthentication(reason);
console.log('ServerBiometricAuthenticationScreen - initiateIDVServerBiometricAuthentication sync response successful');
// Sync success means API accepted the request
// The authentication result will come via onIDVServerBiometricAuthenticationResult event
console.log('ServerBiometricAuthenticationScreen - Server biometric authentication initiated, waiting for result event');
} catch (error) {
console.error('ServerBiometricAuthenticationScreen - initiateIDVServerBiometricAuthentication sync error:', error);
const errorMessage = error.error?.errorString || 'Unknown error';
alert(`Authentication Error\n\nFailed to initiate authentication: ${errorMessage}`);
this.setAuthenticating(false);
}
},
/**
* Handles server biometric authentication result event
* Implements navigate-back-before-modal pattern
* Challenge Mode: 25
*/
handleServerBiometricAuthenticationResultResponse(data) {
console.log('ServerBiometricAuthenticationScreen - Server biometric authentication result received');
// Reset button state
this.setAuthenticating(false);
// Navigate back to ServerBiometricAuthentication screen
console.log('ServerBiometricAuthenticationScreen - Navigating back to ServerBiometricAuthentication screen');
NavigationService.navigate('ServerBiometricAuthentication');
const errorCode = data.error?.longErrorCode;
const statusCode = data.status?.statusCode;
// Small delay to ensure navigation completes before showing modal
setTimeout(() => {
// First check: error code
if (errorCode !== 0) {
console.log('ServerBiometricAuthenticationScreen - Error in authentication:', errorCode);
this.state.modalTitle = 'Authentication Error';
this.state.modalMessage = `${data.error?.errorString}\n\n(Error Code: ${errorCode})`;
this.state.isSuccess = false;
this.showModal();
return;
}
// Second check: status code
if (statusCode !== 0) {
console.log('ServerBiometricAuthenticationScreen - Status error:', statusCode);
this.state.modalTitle = 'Authentication Failed';
this.state.modalMessage = `Status: ${data.status?.statusMessage}\n\n(Status Code: ${statusCode})`;
this.state.isSuccess = false;
this.showModal();
return;
}
// Success - parse idvResponse to check audit status
try {
const idvResponseObj = JSON.parse(data.idvResponse);
console.log('ServerBiometricAuthenticationScreen - Parsed authentication result:', idvResponseObj);
const auditStatus = idvResponseObj.idv_audit_info?.status;
const orchestrationUseCase = idvResponseObj.idv_audit_info?.orchestration_use_case;
const transactionId = idvResponseObj.idv_audit_info?.idv_audit_transaction_id;
const attemptsLeft = idvResponseObj.attempts_left;
if (auditStatus === 'SUCCESS') {
this.state.modalTitle = '✅ Authentication Success';
this.state.modalMessage = `Audit Status: ${auditStatus}\n\n` +
`Use Case: ${orchestrationUseCase || 'N/A'}\n` +
`Transaction ID: ${transactionId || 'N/A'}\n` +
`Attempts Left: ${attemptsLeft}`;
this.state.isSuccess = true;
} else {
this.state.modalTitle = '❌ Authentication Failed';
this.state.modalMessage = `Audit Status: ${auditStatus || 'Unknown'}\n\n` +
`Use Case: ${orchestrationUseCase || 'N/A'}\n` +
`Transaction ID: ${transactionId || 'N/A'}\n` +
`Attempts Left: ${attemptsLeft}`;
this.state.isSuccess = false;
}
this.showModal();
} catch (parseError) {
console.error('ServerBiometricAuthenticationScreen - Failed to parse idvResponse:', parseError);
this.state.modalTitle = 'Parse Error';
this.state.modalMessage = 'Failed to parse authentication result';
this.state.isSuccess = false;
this.showModal();
}
}, 300);
},
/**
* Update template status UI
*/
updateTemplateStatusUI() {
const statusSection = document.getElementById('template-status-section');
if (!statusSection) return;
if (this.state.templateStatus.checked) {
statusSection.style.display = 'block';
const statusTitle = document.getElementById('template-status-title');
const statusText = document.getElementById('template-status-text');
const statusCode = document.getElementById('template-status-code');
if (statusTitle) {
statusTitle.textContent = this.state.templateStatus.exists
? '✅ Biometric Template Status'
: '⚠️ Biometric Template Status';
}
if (statusText) {
statusText.textContent = this.state.templateStatus.exists
? 'Your biometric template is stored on the server. You can proceed with authentication.'
: 'No biometric template found on the server. Please complete biometric opt-in first.';
}
if (statusCode) {
statusCode.textContent = `Status: ${this.state.templateStatus.statusMessage} (Code: ${this.state.templateStatus.statusCode})`;
}
// Update button state
const startButton = document.getElementById('start-authentication-button');
if (startButton) {
startButton.disabled = this.state.isAuthenticating ||
this.state.isCheckingStatus ||
!this.state.templateStatus.exists;
}
// Show/hide disabled note
const disabledNote = document.getElementById('disabled-note');
if (disabledNote) {
disabledNote.style.display = !this.state.templateStatus.exists ? 'block' : 'none';
}
}
},
/**
* Set checking status
*/
setCheckingStatus(isChecking) {
this.state.isCheckingStatus = isChecking;
// Update button state
const startButton = document.getElementById('start-authentication-button');
if (startButton) {
startButton.disabled = this.state.isAuthenticating ||
this.state.isCheckingStatus ||
!this.state.templateStatus.exists;
if (isChecking) {
startButton.textContent = 'Checking Template...';
} else {
startButton.textContent = this.state.isAuthenticating ? 'Authenticating...' : 'Start Authentication';
}
}
},
/**
* Set authenticating status
*/
setAuthenticating(isAuthenticating) {
this.state.isAuthenticating = isAuthenticating;
// Update button state
const startButton = document.getElementById('start-authentication-button');
if (startButton) {
startButton.disabled = this.state.isAuthenticating ||
this.state.isCheckingStatus ||
!this.state.templateStatus.exists;
startButton.textContent = isAuthenticating ? 'Authenticating...' : 'Start Authentication';
}
},
/**
* Show modal
*/
showModal() {
const modal = document.getElementById('server-biometric-modal');
const title = document.getElementById('server-biometric-modal-title');
const message = document.getElementById('server-biometric-modal-message');
const okBtn = document.getElementById('server-biometric-modal-ok-btn');
const dashboardBtn = document.getElementById('server-biometric-modal-dashboard-btn');
if (modal && title && message) {
title.textContent = this.state.modalTitle;
message.textContent = this.state.modalMessage;
// Show/hide buttons based on success state
if (okBtn) {
okBtn.style.display = this.state.isSuccess ? 'none' : 'inline-block';
}
if (dashboardBtn) {
dashboardBtn.style.display = this.state.isSuccess ? 'inline-block' : 'none';
}
modal.style.display = 'flex';
}
},
/**
* Hide modal
*/
hideModal() {
const modal = document.getElementById('server-biometric-modal');
if (modal) {
modal.style.display = 'none';
}
},
/**
* Handle modal OK button
*/
handleModalOK() {
console.log('ServerBiometricAuthenticationScreen - Modal OK clicked');
this.hideModal();
},
/**
* Handle modal Dashboard button
*/
handleModalDashboard() {
console.log('ServerBiometricAuthenticationScreen - Modal Dashboard clicked');
this.hideModal();
NavigationService.navigate('Dashboard');
}
};
// Expose screen to global scope for NavigationService
window.ServerBiometricAuthenticationScreen = ServerBiometricAuthenticationScreen;
Biometric Authentication Screen |
Biometric Authentication Screen |
When the template exists (status code 100), a green banner displays "✅ Your biometric template is stored on the server" and the "Start Authentication" button is enabled (blue).

When no template is found (status code 600), a yellow banner displays "⚠️ No biometric template found on the server" with help text, and the "Start Authentication" button is disabled (grey).
onContentLoaded(params) instead of React hooksthis.state object instead of useStatesetupEventListeners() for persistencegetElementById()Add the following template to your www/index.html file:
<!-- Server Biometric Authentication Screen Template -->
<template id="ServerBiometricAuthentication-template">
<div class="screen-container">
<!-- Header with Menu Button -->
<div class="dashboard-header">
<button id="server-biometric-menu-button" class="menu-button">☰</button>
<h2 class="dashboard-title">Biometric Authentication</h2>
<div class="header-spacer"></div>
</div>
<div class="kyc-content">
<!-- Template Status Section (hidden initially) -->
<div id="template-status-section" class="card" style="display: none;">
<h3 id="template-status-title" class="requirements-title">
✅ Biometric Template Status
</h3>
<p id="template-status-text" class="card-description">
Your biometric template is stored on the server. You can proceed with authentication.
</p>
<p id="template-status-code" class="card-description" style="font-style: italic; color: #666;">
Status: Ready (Code: 100)
</p>
</div>
<!-- Description Section -->
<div class="card">
<h2 class="card-title">Server-Side Biometric Verification</h2>
<p class="card-description">
Server biometric authentication verifies your identity by comparing your live selfie
with your stored biometric template on the server.
</p>
</div>
<!-- Process Section -->
<div class="card">
<h3 class="requirements-title">🔄 Authentication Process</h3>
<ul class="requirements-list">
<li>SDK captures your live selfie</li>
<li>Liveness detection verifies you are a real person</li>
<li>Face matching compares selfie with stored template</li>
<li>Server processes and returns authentication result</li>
</ul>
</div>
<!-- Guidelines Section -->
<div class="card">
<h3 class="requirements-title">📋 Guidelines</h3>
<ul class="requirements-list">
<li>Ensure good lighting for accurate face detection</li>
<li>Look directly at the camera</li>
<li>Remove glasses or face coverings if requested</li>
<li>Follow on-screen instructions during capture</li>
</ul>
</div>
<!-- Start Button -->
<div class="button-container">
<button id="start-authentication-button" class="primary-button">
Start Authentication
</button>
</div>
<!-- Disabled Note (shown when template doesn't exist) -->
<p id="disabled-note" class="error-note" style="display: none;">
⚠️ Authentication is disabled because no biometric template was found on the server.
Please complete biometric opt-in first (e.g., KYC flow).
</p>
</div>
</div>
</template>
<!-- Server Biometric Authentication Result Modal -->
<div id="server-biometric-modal" class="modal-overlay" style="display: none;">
<div class="modal-container">
<div class="modal-content">
<h2 id="server-biometric-modal-title" class="modal-title"></h2>
<p id="server-biometric-modal-message" class="modal-message"></p>
<div class="modal-buttons">
<button id="server-biometric-modal-ok-btn" class="primary-button">OK</button>
<button id="server-biometric-modal-dashboard-btn" class="primary-button">
Back to Dashboard
</button>
</div>
</div>
</div>
</div>
Add the following styles to your www/css/index.css file:
/* Server Biometric Authentication Screen Styles */
#template-status-section {
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
#template-status-section.warning {
background-color: #fff3cd;
border-color: #ffeeba;
}
#disabled-note {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 8px;
color: #721c24;
padding: 15px;
margin-top: 20px;
text-align: center;
font-size: 14px;
line-height: 1.6;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-container {
background-color: white;
border-radius: 12px;
max-width: 90%;
max-height: 80%;
overflow-y: auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.modal-content {
padding: 30px;
}
.modal-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #2c3e50;
text-align: center;
}
.modal-message {
font-size: 16px;
color: #555;
margin-bottom: 30px;
white-space: pre-line;
line-height: 1.6;
text-align: center;
word-wrap: break-word;
max-width: 100%;
}
.modal-buttons {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.modal-buttons .primary-button {
min-width: 150px;
}
ServerBiometricAuthentication-templateThe screen is already integrated into the NavigationService via the template ID. When you call:
NavigationService.navigate('ServerBiometricAuthentication');
The NavigationService will:
ServerBiometricAuthentication-templatewindow.ServerBiometricAuthenticationScreen.onContentLoaded()Update your drawer menu HTML in index.html:
<!-- Drawer Menu -->
<div id="drawer-menu" class="drawer-menu">
<div class="drawer-header">
<h2>Menu</h2>
<button id="drawer-close-button" class="drawer-close-button">✕</button>
</div>
<div class="drawer-content">
<!-- Existing menu items -->
<button class="drawer-menu-item" onclick="NavigationService.navigate('Dashboard')">
🏠 Dashboard
</button>
<!-- Server Biometric Authentication Menu Item -->
<button class="drawer-menu-item" onclick="NavigationService.navigate('ServerBiometricAuthentication')">
🔐 Biometric Authentication
</button>
<!-- Other menu items -->
</div>
</div>
window for NavigationService accessTest the complete server biometric authentication flow:
1. Launch the app
2. Complete user login (activated customer)
3. Navigate to Dashboard
4. Open drawer menu (hamburger icon)
5. Select "🔐 Biometric Authentication"
1. Screen opens and automatically checks template status
2. Observe loading state: Button text changes to "Checking Template..."
3. Template status displayed:
- ✅ If template exists (status code 100):
- Green banner with "Your biometric template is stored on the server"
- "Start Authentication" button enabled
- ⚠️ If no template (status code 600):
- Yellow banner with "No biometric template found on the server"
- "Start Authentication" button disabled (grey)
- Red warning note: "Authentication is disabled..."
1. Click "Start Authentication" button
2. Button shows loading: "Authenticating..."
3. SDK initiates workflow 8 (selfie capture)
4. Selfie confirmation screen appears (reused from IDV MFA flow)
1. Click "Start Selfie Capture" button
→ Native camera opens with Aware FaceCapture
→ Liveness detection guides user
→ User captures selfie
→ Camera closes
2. OR Click Close button (X)
→ Error 240 triggered (user cancelled)
→ Result event fires immediately
1. Navigation back to ServerBiometricAuthentication screen
2. 300ms delay
3. Result modal appears:
Success Modal (audit status = SUCCESS):
✅ Authentication Success
- Audit Status: SUCCESS
- Use Case: RDNA_IDV_POSTLOGIN_IDV_SELFIE_BIOMETRIC
- Transaction ID: [unique ID]
- Attempts Left: [number]
- Button: "Back to Dashboard"
Failure Modal (audit status = FAILURE):
❌ Authentication Failed
- Audit Status: FAILURE
- Use Case: RDNA_IDV_POSTLOGIN_IDV_SELFIE_BIOMETRIC
- Transaction ID: [unique ID]
- Attempts Left: [number]
- Button: "OK"
Cancellation (error 240):
Authentication Error
- User Cancelled
- Error Code: 240
- Button: "OK"
✅ Template check returns status code 100
✅ Button enabled
✅ User clicks "Start Authentication"
✅ Selfie capture completes
✅ Authentication succeeds (audit status = SUCCESS)
✅ Success modal shown
✅ User clicks "Back to Dashboard"
✅ Navigates to Dashboard
⚠️ Template check returns status code 600
⚠️ Button disabled (grey color)
⚠️ Yellow banner: "No biometric template found"
⚠️ Red warning note shown
⚠️ User cannot proceed with authentication
✅ Template check succeeds (status code 100)
✅ User clicks "Start Authentication"
✅ Selfie confirmation screen appears
❌ User clicks Close button (X)
❌ Error 240 triggered
❌ Navigate back to authentication screen
❌ Error modal: "User Cancelled (Error Code: 240)"
✅ User clicks "OK"
✅ Button re-enabled, can retry
✅ Template check succeeds
✅ User completes selfie capture
❌ Server determines face doesn't match
❌ Audit status = FAILURE
❌ Navigate back to authentication screen
❌ Failure modal with audit data
✅ User can retry authentication
✅ User completes authentication (success or failure)
✅ User navigates to Dashboard
✅ User returns to Biometric Authentication from drawer menu
✅ Screen loads → onContentLoaded() triggers
✅ Template status check runs automatically
✅ UI state reset (isAuthenticating = false)
✅ Template status displayed
✅ User can perform another authentication
Use this checklist to verify your implementation:
iOS: Safari → Develop → Simulator → [Your App] Android: Chrome → chrome://inspect → Inspect
Check console logs for comprehensive debugging information including:
# Prepare platforms (copies www/ files and runs hooks)
cordova prepare
# Build for iOS
cordova build ios
# Run on iOS
cordova run ios
# Build for Android
cordova build android
# Run on Android
cordova run android
# For iOS (opens in Xcode)
open platforms/ios/*.xcworkspace
# For Android (import into Android Studio)
# File → Open → select platforms/android/
cordova prepare to sync www/ files to platformscordova plugin remove cordova-plugin-rdna
cordova plugin add ./RdnaClient
cordova prepare
cordova platform remove ios
cordova platform add ios
cordova prepare ios
Template check returns 600 but user has completed KYC
Template check stuck on "Checking..."
setCheckUserBiometricTemplateStatusHandler() called in setupEventListeners()Button remains disabled after template check succeeds
updateTemplateStatusUI() is called in event handlerAuthentication fails with error 240
Authentication result never arrives
setServerBiometricAuthenticationResultHandler() called in setupEventListeners()Modal doesn't appear after authentication
showModal() callWrong modal buttons shown
isSuccess state not set correctlyidvResponseObj.idv_audit_info?.status === 'SUCCESS'Screen doesn't load
ServerBiometricAuthentication-template in index.htmlDrawer menu doesn't work
setupEventListeners() registers menu button onclick handlerNavigation back doesn't work
# Check installed plugins
cordova plugin ls
# Check installed platforms
cordova platform ls
# View plugin installation details
cordova plugin ls --verbose
# Clean and rebuild
cordova clean
cordova build ios
checkIDVUserBiometricTemplateStatus() automatically in onContentLoaded(), not on button presstemplateStatus.exists && !isCheckingStatus && !isAuthenticatingisAuthenticating and isCheckingStatus to false in onContentLoaded()onIDVCheckUserBiometricTemplateStatus for template check, onIDVServerBiometricAuthenticationResult for auth resultNavigationService.navigate('ServerBiometricAuthentication') from result handler.bind(this) when registering handlers to maintain screen contextthis.state object for easy accesscom.uniken.rdnaplugin.RdnaClient.RDNALoggingLevel.RDNA_NO_LOGS in productioncordova prepare after www/ file changesYou've successfully implemented the REL-ID IDV Post-Login Server Biometric Authentication Flow in your Cordova application!
✅ Template Status Validation: Implemented automatic biometric template checking with status codes 100/600
✅ Server Biometric Authentication: Built complete authentication workflow with workflow 8 and challenge mode 25
✅ Screen-Level Event Handlers: Mastered persistent event handlers during native UI workflows
✅ Navigate-Back-Before-Modal Pattern: Implemented proper navigation for result display
✅ Audit Data Display: Parsed and displayed comprehensive authentication audit information
✅ Error Handling: Gracefully handled error 240 (user cancellation) and authentication failures
✅ Production-Ready Implementation: Built robust, user-friendly biometric verification experience
idv_audit_info.status for SUCCESS/FAILURE validationcom.uniken.rdnaplugin.RdnaClient (no imports needed)document.addEventListener()getElementById()Now that you've mastered server biometric authentication, consider:
🔐 Thank you for completing the REL-ID IDV Post-Login Server Biometric Authentication Flow codelab!
You're now equipped to implement production-ready server-side biometric verification for activated customers with stored templates, providing secure and convenient identity verification with liveness detection and comprehensive audit trails using Cordova Single Page Application architecture.