🎯 Learning Path:

  1. Complete REL-ID Complete Activation & Login Flow Codelab
  2. Complete REL-ID Data Signing Flow Codelab
  3. You are here β†’ Add IDV Server Biometric Step-Up (Workflow 16)

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.

What You'll Build

You'll enhance your existing data signing app with:

What You'll Learn

By completing this codelab, you'll master:

  1. IDV Workflow 16 Integration: Implementing authLevel = 4 with authenticatorType = 1 for server biometric
  2. NavigationRef Pattern: Direct navigation state access for cross-hierarchy screen management
  3. Selfie Screen Detection: Detecting and closing IDVSelfieProcessStart screen in provider-level handler
  4. Screen Transition Timing: Adding 300ms delay after selfie closure for smooth UI updates
  5. Error Priority Checking: Implementing proper error handling (error code first, then status code)
  6. Provider-Level Architecture: Understanding why data signing uses provider-level vs screen-level handlers

Prerequisites

Before starting, ensure you have:

Get the Code

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.

What is IDV Workflow 16?

IDV Workflow 16 is the server biometric step-up authentication flow specifically designed for data signing operations. Unlike other IDV workflows, workflow 16:

IDV Workflow Comparison

IDV 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

Why Provider-Level Handler?

Unlike notifications (workflow 9) which use screen-level handlers, data signing uses a provider-level handler because:

  1. No Modal Management: Data signing has no modal to close (unlike notification action modal)
  2. Centralized State: All data signing state lives in SDKEventProvider context
  3. Clean Architecture: One place for all data signing event handling
  4. Reusability: Can be used from any screen that implements data signing
  5. Simpler Implementation: No need for handler chaining or focus effects

Server Biometric Event Flow

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

When to Use Server Biometric (AuthLevel 4)?

Use authLevel = 4 with authenticatorType = 1 for:

Let's understand the technical architecture we'll implement for IDV workflow 16 in SDKEventProvider.tsx.

Provider-Level Handler Pattern

We'll modify SDKEventProvider.tsx to add three key pieces:

  1. NavigationRef Import: Direct access to navigation state
  2. Error Priority Processing: Function to check errors before showing results
  3. Selfie Screen Auto-Closure: Logic to detect and close selfie screen

Key Technical Concepts

1. NavigationRef Pattern

Why NavigationRef?

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
}

2. Error Priority Checking

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();

3. Screen Transition Delay

Why 300ms?

if (selfieScreenWasClosed) {
  setTimeout(() => {
    processDataSigningResult(response);
  }, 300); // Wait for transition
  return;
}

Implementation Flow

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

Files You'll Modify

In this codelab, you'll modify only one file:

File

Purpose

Changes

src/uniken/providers/SDKEventProvider.tsx

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.

Add NavigationRef Import

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';

Why This Import?

The navigationRef export from NavigationService.ts provides:

Verify NavigationService Export

Your NavigationService.ts should already have this export from the MFA codelab:

// src/tutorial/navigation/NavigationService.ts
export const navigationRef = createNavigationContainerRef<RootStackParamList>();

What's Next?

Now that we have navigationRef imported, we'll use it to:

  1. Detect when IDVSelfieProcessStart screen is active
  2. Close the screen with 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.

Add processDataSigningResult Function

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');
}, []);

Understanding Error Priority

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');

Key Features

  1. State Updates: Updates signing result, clears loading state, hides password modal
  2. Priority Checking: Error code checked before status code
  3. Early Return: Stops processing on error (prevents navigation behind alerts)
  4. User-Friendly Messages: Uses DataSigningService.getErrorMessage() for readable errors
  5. Form Preservation: Keeps form data for retry on error

What's Next?

Now we have error priority processing ready. In the next step, we'll implement the main handler that:

  1. Detects if selfie screen is active
  2. Closes it if present
  3. Waits 300ms for transition
  4. Calls our processDataSigningResult function

Now let's implement the main handler that detects and closes the selfie screen before processing results.

Replace handleDataSigningResponse Function

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]);

Understanding the Implementation

Part 1: Screen Detection Function

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:

Part 2: Conditional Processing

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?:

Complete Event Flow

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

Common Scenarios

Scenario 1: Server Biometric Success

Scenario 2: Server Biometric Error

Scenario 3: Password/LDA Success

Scenario 4: Password/LDA Error

Error Handling

The implementation handles:

What's Next?

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.

Test 1: Server Biometric Success Flow

  1. Fill Data Signing Form:
    • Payload: "Transfer $10,000 to account 123456"
    • Auth Level: "RDNA_AUTH_LEVEL_4 (4)"
    • Authenticator Type: "RDNA_IDV_SERVER_BIOMETRIC (1)"
    • Reason: "High-value financial transaction"
  2. Click "Sign Data" β†’ Loading state activates
  3. Verify Selfie Screen:
    • βœ… IDVSelfieProcessStart screen appears
    • βœ… Screen shows data signing specific guidelines
    • βœ… "Start Selfie Capture" button visible
  4. Complete Biometric Capture:
    • Tap "Start Selfie Capture"
    • Complete selfie with liveness detection
    • Wait for server verification
  5. Verify Auto-Closure:
    • βœ… Selfie screen automatically closes
    • βœ… DataSigningResultScreen appears (not behind selfie screen)
    • βœ… No flicker or UI glitches
    • βœ… Signature details all present

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

Test 2: Server Biometric Error Handling

  1. Trigger Error Scenario:
    • Use same form values as Test 1
    • Cancel biometric capture OR
    • Fail liveness detection
  2. Verify Error Handling:
    • βœ… Selfie screen closes automatically
    • βœ… Alert appears on DataSigningInputScreen (not behind selfie screen)
    • βœ… Alert shows user-friendly error message
    • βœ… Form data preserved for retry

Expected Error Messages:

Test 3: Password/LDA Flow (No Selfie Screen)

  1. Test Password Flow:
    • Auth Level: "RDNA_AUTH_LEVEL_3 (3)"
    • Authenticator Type: "RDNA_AUTH_PASS (2)"
    • Complete password entry
    • βœ… Results appear immediately (no 300ms delay)
    • βœ… No selfie screen involved
  2. Test LDA Flow:
    • Auth Level: "RDNA_AUTH_LEVEL_3 (3)"
    • Authenticator Type: "RDNA_AUTH_LDA (3)"
    • Complete biometric prompt
    • βœ… Results appear immediately
    • βœ… No screen closure logic triggered

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

Test 4: Navigation Hierarchy

  1. Test from Different Screens:
    • Launch from dashboard
    • Launch from drawer menu
    • Launch after deep link
    • βœ… All cases should detect and close selfie screen correctly
  2. Test Navigation State:
    • Go back during selfie screen (before capture)
    • βœ… No errors in logs
    • βœ… State resets properly

Verification Checklist

Core Functionality:

Error Handling:

Other Authentication Methods:

Edge Cases:

Common Issues & Solutions

Issue: Selfie screen doesn't close

Issue: Alert appears behind selfie screen

Issue: Results appear for error responses

Issue: Password/LDA delayed unnecessarily

πŸŽ‰ You Did It!

You've successfully added IDV Server Biometric Step-Up Authentication (Workflow 16) to your data signing implementation!

What You Accomplished

Key Implementation Patterns

NavigationRef 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);

Production Checklist

Before deploying, ensure:

Next Steps

Explore More REL-ID Features:

Enhance Your Implementation:

Additional Resources

πŸ” 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!