🎯 Learning Path:

  1. Complete REL-ID Complete Activation & Login Flow Codelab
  2. Complete REL-ID Additional Device Activation Flow With Notifications Codelab
  3. Complete REL-ID Step-Up Authentication with Notifications (Password)
  4. You are here → Server Biometric Step-Up Authentication with IDV Workflow 9

Welcome to the REL-ID Server Biometric Step-Up Authentication codelab! This tutorial enhances your notification application with high-security biometric verification using liveness detection for sensitive notification actions.

What You'll Build

In this codelab, you'll enhance your existing notification application with:

What You'll Learn

By completing this codelab, you'll master:

  1. Server Biometric Flow: Understanding authLevel=4 and how it differs from password (challengeMode 3)
  2. IDV Workflow 9: Implementing notification-specific selfie capture with liveness detection
  3. Event Handler Registration: Using useEffect + useFocusEffect pattern for reliable handler registration
  4. Handler Chaining: Preserving global handlers while adding screen-specific logic
  5. Modal Lifecycle Management: Closing action modal before selfie screen appears
  6. Parent Navigator Pattern: Navigating across drawer/stack hierarchies to close screens
  7. Screen Transition Delays: Ensuring smooth UI updates with proper timing
  8. Liveness Detection: Integrating Aware FaceCapture library for anti-spoofing

Prerequisites

Before starting this codelab, ensure you have:

Get the Code from GitHub

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-public/codelab-react-native.git

Navigate to the relid-IDV-post-login-step-up-auth-notification folder in the repository you cloned earlier

Codelab Architecture Overview

This codelab adds server biometric step-up authentication to your notification application with three core enhancements:

  1. IDV Event Handler: getIDVSelfieProcessStartConfirmation handler for workflow 9 with dual registration pattern
  2. Action Modal Management: Close action modal before selfie screen appears to prevent UI blocking
  3. Parent Navigator Pattern: Cross-hierarchy screen closure using navigation.getParent() for smooth transitions

Before implementing server biometric authentication, let's understand the key differences from password step-up and the unique event flow.

Password vs Server Biometric Step-Up

The password step-up codelab (prerequisite) uses Challenge Mode 3 for re-authentication. Server biometric authentication uses a different mechanism:

Aspect

Password Step-Up (Prerequisite)

Server Biometric Step-Up (This Codelab)

Backend Configuration

authLevel = 1

authLevel = 4

SDK Event Triggered

getPassword event

getIDVSelfieProcessStartConfirmation event

Event Parameter

challengeMode = 3

idvWorkflow = 9

UI Pattern

Modal dialog (StepUpPasswordDialog)

Full screen navigation (IDVSelfieProcessStart)

Authentication Method

Password input verification

Selfie capture with liveness detection

Backend Verification

Password hash verification

Biometric template matching

Security Level

Standard re-authentication

High-security with anti-spoofing

Server Biometric Event Flow

The server biometric step-up authentication follows this event-driven pattern:

User Taps Notification Action → updateNotification(uuid, action) API Called →
Backend Determines authLevel = 4 (Server Biometric Required) →

SDK Triggers: getIDVSelfieProcessStartConfirmation Event (idvWorkflow: 9) →

[Screen-Level Handler Executes FIRST]:
  handleSelfieProcessStartForNotification() →
  Checks if data.idvWorkflow === 9 →
  Calls setShowActionModal(false) → Action Modal Closes →

[Global Handler Executes SECOND]:
  originalGetSelfieHandler() (from SDKIDVEventProvider) →
  Navigates to IDVSelfieProcessStart Screen →

User Reviews Guidelines → Taps "Start Selfie Capture" →
SDK Captures Selfie → Liveness Detection (Aware FaceCapture) →
SDK Verifies Biometric Template on Server →

SDK Triggers: onUpdateNotification Event →

[Screen-Level Handler Executes]:
  handleUpdateNotificationReceived() →
  Detects Selfie Screen via Parent Navigator →
  Closes Selfie Screen with parentNav.goBack() →
  Delays 300ms for Screen Transition →
  Displays Success/Error Alert →
  Navigates to Dashboard

AuthLevel Configuration

Important: The authentication method is determined by the authLevel configured in the backend for each notification action:

AuthLevel

Authentication Method

SDK Behavior

Use Case

1

Password

SDK triggers getPassword with challengeMode 3

Standard security actions (prerequisite codelab)

3

LDA (Local Device Auth)

SDK handles biometric internally

Standard biometric actions

4

Server Biometric

SDK triggers getIDVSelfieProcessStartConfirmation with idvWorkflow 9

High-security actions requiring liveness

IDV Workflow 9 - Notification Step-Up

IDV Workflow 9 is specifically designed for notification step-up authentication:

IDV Workflow

Purpose

When Used

Screen Navigation

9

Notification Step-Up Authentication

Sensitive notification actions with authLevel=4

Must close action modal first

10

Biometric Opt-In

User enrolls in biometric authentication

Standalone flow

6

Post-Login KYC

Know Your Customer verification

Standalone flow

8

Post-Login Selfie

Biometric authentication after login

Standalone flow

15

Login Selfie

Biometric verification during login

Pre-login flow

Key Server Biometric Components

The REL-ID SDK uses these components for server biometric step-up:

Component

Description

Developer Action

Aware FaceCapture Library

Embedded in react-native-rdna-client for liveness detection

Pre-configured, no action needed

getIDVSelfieProcessStartConfirmation

SDK event when selfie capture is required (idvWorkflow: 9)

Register handler in GetNotificationsScreen

IDVSelfieProcessStart Screen

Selfie capture initiation screen with guidelines

Already implemented in project

onUpdateNotification

SDK event with authentication result

Handle result, close selfie screen, show alert

Now let's implement the screen-level event handler that intercepts getIDVSelfieProcessStartConfirmation events for workflow 9.

Understanding Handler Chaining Pattern

For server biometric step-up, we need two actions when the selfie event is triggered:

  1. Screen-Level Logic: Close the action modal to prevent UI blocking
  2. Global Navigation: Navigate to IDVSelfieProcessStart screen

This requires a handler chaining pattern where our screen handler executes first, then calls the global handler:

// Screen handler executes first
handleSelfieProcessStartForNotification(data); // Close modal

// Then call global handler
if (originalHandler) originalHandler(data); // Navigate to selfie screen

Implement Selfie Process Start Handler

Add the handler that intercepts workflow 9 events to close the action modal:

// src/tutorial/screens/notification/GetNotificationsScreen.tsx

// Add imports at the top
import { useFocusEffect } from '@react-navigation/native';
import rdnaIDVService from '../../../uniken/services/idv/rdnaIDVService';
import type {
  RDNAIDVGetSelfieProcessStartConfirmationData
} from '../../../uniken/types/idv/rdnaIDVEvents';

/**
 * Handle selfie process start for notification step-up (idvWorkflow: 9)
 *
 * CRITICAL: Close action modal BEFORE SDK navigates to selfie screen
 */
const handleSelfieProcessStartForNotification = useCallback(
  (data: RDNAIDVGetSelfieProcessStartConfirmationData) => {
    console.log('GetNotificationsScreen - Selfie process start confirmation received:', {
      idvWorkflow: data.idvWorkflow,
      challengeMode: data.challengeMode
    });

    // Check if this is notification step-up flow (idvWorkflow: 9)
    if (data.idvWorkflow === 9) {
      console.log('GetNotificationsScreen - Server biometric step-up for notification detected');

      // CRITICAL: Close action modal to allow selfie screen to appear on top
      setShowActionModal(false);

      console.log('GetNotificationsScreen - Action modal closed, selfie screen will appear');

      // SDK will automatically navigate to IDVSelfieProcessStart screen
      // Global handler in SDKIDVEventProvider will handle navigation
    } else {
      console.log('GetNotificationsScreen - Different IDV workflow, ignoring:', data.idvWorkflow);
    }
  },
  []
);

Register Handler with Dual Pattern

Use both useEffect and useFocusEffect to ensure the handler persists through navigation events:

// src/tutorial/screens/notification/GetNotificationsScreen.tsx

/**
 * Register event handlers on mount
 * Includes IDV selfie handler for server biometric step-up
 */
useEffect(() => {
  loadNotifications();

  const eventManager = rdnaService.getEventManager();
  const idvEventManager = rdnaIDVService.getEventManager();

  // Register notification handlers
  eventManager.setGetNotificationsHandler(handleNotificationsReceived);
  eventManager.setUpdateNotificationHandler(handleUpdateNotificationReceived);

  // Register password handler (challengeMode 3) - from prerequisite codelab
  const originalGetPasswordHandler = (eventManager as any).getPasswordHandler;
  const wrappedGetPasswordHandler = (data: RDNAGetPasswordData) => {
    handleGetPasswordStepUp(data, originalGetPasswordHandler);
  };
  eventManager.setGetPasswordHandler(wrappedGetPasswordHandler);

  // NEW: Register IDV selfie handler for server biometric step-up
  console.log('GetNotificationsScreen - Registering IDV selfie handler');
  const originalGetSelfieHandler = (idvEventManager as any).getSelfieProcessStartConfirmationHandler;
  console.log('GetNotificationsScreen - Original selfie handler exists:', !!originalGetSelfieHandler);

  const wrappedGetSelfieHandler = (data: RDNAIDVGetSelfieProcessStartConfirmationData) => {
    // CRITICAL: Screen handler executes FIRST (close modal)
    handleSelfieProcessStartForNotification(data);

    // THEN: Global handler executes (navigate to selfie screen)
    if (originalGetSelfieHandler) {
      originalGetSelfieHandler(data);
    }
  };

  idvEventManager.setGetSelfieProcessStartConfirmationHandler(wrappedGetSelfieHandler);
  console.log('GetNotificationsScreen - IDV selfie handler registered');

  // Cleanup: restore original handlers
  return () => {
    eventManager.setGetNotificationsHandler(undefined);
    eventManager.setUpdateNotificationHandler(undefined);
    eventManager.setGetPasswordHandler(originalGetPasswordHandler);
    idvEventManager.setGetSelfieProcessStartConfirmationHandler(originalGetSelfieHandler);
  };
}, [
  handleNotificationsReceived,
  handleUpdateNotificationReceived,
  handleGetPasswordStepUp,
  handleSelfieProcessStartForNotification
]);

Add Focus-Based Re-Registration

Add useFocusEffect to ensure the handler remains active after navigation:

// src/tutorial/screens/notification/GetNotificationsScreen.tsx

/**
 * Re-register IDV selfie handler when screen is focused
 *
 * CRITICAL: This ensures our handler is active even after navigation events
 */
useFocusEffect(
  useCallback(() => {
    console.log('GetNotificationsScreen - Screen focused, re-registering IDV selfie handler');

    const idvEventManager = rdnaIDVService.getEventManager();
    const originalGetSelfieHandler = (idvEventManager as any).getSelfieProcessStartConfirmationHandler;

    const wrappedGetSelfieHandler = (data: RDNAIDVGetSelfieProcessStartConfirmationData) => {
      handleSelfieProcessStartForNotification(data);
      if (originalGetSelfieHandler) {
        originalGetSelfieHandler(data);
      }
    };

    idvEventManager.setGetSelfieProcessStartConfirmationHandler(wrappedGetSelfieHandler);
  }, [handleSelfieProcessStartForNotification])
);

Now let's implement the enhanced onUpdateNotification handler that closes the selfie screen before showing alerts.

Understanding Parent Navigator Pattern

After server biometric verification completes, the onUpdateNotification event is triggered. At this point, the selfie screen (IDVSelfieProcessStart) may still be visible on top of the GetNotificationsScreen.

We need to:

  1. Detect if the selfie screen is currently visible
  2. Close the selfie screen using parent navigator
  3. Delay 300ms for smooth screen transition
  4. Display success/error alert on GetNotificationsScreen

The parent navigator pattern allows us to navigate across hierarchies:

DrawerNavigator (parent)
  ├─ GetNotificationsScreen (current screen)
  └─ IDVSelfieProcessStart (modal screen on top)

To close the selfie screen from GetNotificationsScreen, we access the parent navigator:

const parentNav = navigation.getParent(); // Get DrawerNavigator
const state = parentNav.getState(); // Get navigation state
const currentRoute = state.routes[state.index]; // Get top screen
if (currentRoute?.name === 'IDVSelfieProcessStart') {
  parentNav.goBack(); // Close selfie screen
}

Extract Result Processing Logic

First, extract the notification update result processing into a separate function:

// src/tutorial/screens/notification/GetNotificationsScreen.tsx

/**
 * Handle notification update result
 * Extracted logic to process notification update response
 */
const handleNotificationUpdateResult = useCallback((data: RDNAUpdateNotificationData) => {
  console.log('GetNotificationsScreen - Processing notification update result');

  // Check for errors first
  if (data.error.longErrorCode !== 0) {
    const errorMessage = data.error.errorString || 'Failed to update notification';
    setShowStepUpAuth(false);
    Alert.alert('Update Failed', errorMessage);
    return;
  }

  const responseData = data.pArgs?.response;
  const statusCode = responseData?.StatusCode;
  const statusMessage = responseData?.StatusMsg || 'Unknown error occurred';

  if (statusCode === 100) {
    // Success - notification action completed
    setShowStepUpAuth(false);
    setShowActionModal(false);
    Alert.alert(
      'Success',
      statusMessage || 'Notification action completed successfully',
      [{
        text: 'OK',
        onPress: () => {
          navigation.navigate('DrawerNavigator', { screen: 'Dashboard' });
        }
      }]
    );
    loadNotifications();
  } else if (statusCode === 110 || statusCode === 153) {
    // Critical errors - show alert BEFORE SDK logout
    setShowStepUpAuth(false);
    setShowActionModal(false);
    Alert.alert('Authentication Failed', statusMessage);
  } else {
    // Other errors
    setShowStepUpAuth(false);
    Alert.alert('Update Failed', statusMessage);
  }
}, [navigation, loadNotifications]);

Implement Parent Navigator Screen Closure

Enhance the handleUpdateNotificationReceived handler to detect and close the selfie screen:

// src/tutorial/screens/notification/GetNotificationsScreen.tsx

/**
 * Handle update notification response
 *
 * NEW: Checks if selfie screen is active and closes it before showing alerts
 */
const handleUpdateNotificationReceived = useCallback((data: RDNAUpdateNotificationData) => {
  console.log('GetNotificationsScreen - Received update notification event');
  setActionLoading(false);
  setStepUpSubmitting(false);

  /**
   * NEW: Check if selfie screen is active and close it before processing
   * Uses parent navigator pattern to check navigation stack
   */
  const closeSelfieScreenIfPresent = () => {
    console.log('GetNotificationsScreen - Checking if selfie screen needs to be closed');

    try {
      // Get parent navigator (DrawerNavigator)
      const parentNav = navigation.getParent();

      if (parentNav) {
        const state = parentNav.getState();
        const currentRoute = state.routes[state.index];

        // Check if IDVSelfieProcessStart screen is on top
        if (currentRoute?.name === 'IDVSelfieProcessStart') {
          console.log('GetNotificationsScreen - IDVSelfieProcessStart screen detected, closing it');

          // Navigate back to close selfie screen
          parentNav.goBack();

          console.log('GetNotificationsScreen - Selfie screen closed via parent navigator');
          return true; // Screen was present and closed
        } else {
          console.log('GetNotificationsScreen - No IDVSelfieProcessStart screen on top, route:', currentRoute?.name);
        }
      } else {
        console.warn('GetNotificationsScreen - Parent navigator not found');
      }
    } catch (error) {
      console.error('GetNotificationsScreen - Error checking/closing selfie screen:', error);
    }

    return false; // Screen not present or couldn't close
  };

  // Check and close selfie screen if present
  const selfieScreenWasClosed = closeSelfieScreenIfPresent();

  if (selfieScreenWasClosed) {
    // NEW: Delay processing to allow screen transition to complete
    console.log('GetNotificationsScreen - Delaying result processing for screen transition (300ms)');

    setTimeout(() => {
      console.log('GetNotificationsScreen - Processing notification update result after screen close');
      handleNotificationUpdateResult(data);
    }, 300); // 300ms allows smooth screen transition

    return; // Exit early, let setTimeout handle the rest
  }

  // Selfie screen not present (password/LDA step-up), process immediately
  console.log('GetNotificationsScreen - Processing notification update result immediately');
  handleNotificationUpdateResult(data);
}, [navigation, handleNotificationUpdateResult]);

Now let's test the complete server biometric step-up implementation.

Server Configuration

Before testing, ensure your REL-ID server is configured for server biometric step-up:

Test Scenario 1: Server Biometric Success Flow

Test the complete server biometric step-up flow:

  1. Complete MFA flow and log in to dashboard
  2. Navigate to Notifications screen from drawer menu
  3. Verify notifications loaded - Check that getNotifications() succeeded
  4. Tap notification with authLevel=4 - Select action requiring server biometric
  5. Verify action modal appears - Modal displays action details
  6. Submit action - Tap action button
  7. Verify handler execution:
    Console logs should show:
    - "GetNotificationsScreen - Calling updateNotification API"
    - "GetNotificationsScreen - Selfie process start confirmation received"
    - "GetNotificationsScreen - Server biometric step-up for notification detected"
    - "GetNotificationsScreen - Action modal closed, selfie screen will appear"
    - "SDKIDVEventProvider - Selfie process start confirmation requested"
    
  8. Verify modal closes - Action modal should close automatically
  9. Verify selfie screen appears - IDVSelfieProcessStart screen on top
  10. Review workflow 9 guidelines - Screen shows notification step-up specific text
  11. Tap "Start Selfie Capture" - Initiate biometric capture
  12. Complete selfie capture - SDK captures selfie with liveness detection
  13. Verify result handling:
    Console logs should show:
    - "GetNotificationsScreen - Received update notification event"
    - "GetNotificationsScreen - Checking if selfie screen needs to be closed"
    - "GetNotificationsScreen - IDVSelfieProcessStart screen detected, closing it"
    - "GetNotificationsScreen - Selfie screen closed via parent navigator"
    - "GetNotificationsScreen - Delaying result processing for screen transition (300ms)"
    - "GetNotificationsScreen - Processing notification update result after screen close"
    
  14. Verify screen closes - Selfie screen closes with goBack()
  15. Verify alert appears - Success alert displays on GetNotificationsScreen (not behind selfie)
  16. Navigate to dashboard - Tap "OK" button
  17. Verify notifications refresh - Latest notification list loaded

Test Scenario 2: Server Biometric with User Cancellation

Test user cancellation during selfie capture:

  1. Steps 1-9 - Same as Scenario 1 (up to selfie screen appearing)
  2. Tap Cancel/Back - User decides not to complete biometric
  3. Verify selfie screen closes - Returns to GetNotificationsScreen
  4. Verify action modal state - User can retry action if needed

Test Scenario 3: Server Biometric Failure

Test server biometric verification failure:

  1. Steps 1-11 - Same as Scenario 1 (up to selfie capture)
  2. Complete poor quality selfie - Intentionally provide poor lighting/angle
  3. SDK returns failure - Server verification fails
  4. Verify error handling:
    • Selfie screen closes
    • Error alert displays: "Biometric verification failed"
    • User can retry action from notifications screen

Verification Checklist

Use this checklist to verify your implementation:

Let's understand the architecture decisions and troubleshoot common issues.

Why Dual Handler Registration (useEffect + useFocusEffect)?

The implementation uses both useEffect and useFocusEffect for IDV handler registration. This is a deliberate architectural choice:

useEffect

(runs on mount):

useFocusEffect

(runs on screen focus):

Why both are needed:

// Scenario without useFocusEffect:
1. Screen mounts → useEffect registers handler
2. User navigates to selfie screen → SDKIDVEventProvider may re-register
3. User goes back to notifications screen → Screen focused
4. Handler might be lost → useFocusEffect re-registers it

Why Parent Navigator Pattern Instead of State Flag?

Current approach (better): Check parent navigator state directly

Advantages of navigator-based check:

  1. Single source of truth: Navigator state is authoritative
  2. No manual tracking: No need to set/unset flags
  3. No synchronization bugs: Flag can't get out of sync with actual screen state
  4. Works with manual navigation: User can press back button, still detected
// ✅ Better: Navigator state as source of truth
const currentRoute = parentNav.getState().routes[state.index];
if (currentRoute?.name === 'IDVSelfieProcessStart') {
  // Screen is definitely present
}

// ❌ Worse: Manual flag tracking
if (isSelfieScreenActive) {
  // Flag might be out of sync with actual screen state
}

Common Issues and Solutions

Issue 1: Action Modal Not Hiding

Symptoms: Selfie screen appears behind action modal

Solution: Verify handler registration

// Check console logs:
// - "GetNotificationsScreen - Registering IDV selfie handler"
// - "GetNotificationsScreen - IDV selfie handler registered"
// - "GetNotificationsScreen - Screen focused, re-registering IDV selfie handler"

// Ensure useFocusEffect is included
useFocusEffect(
  useCallback(() => {
    // Re-register handler
  }, [handleSelfieProcessStartForNotification])
);

Issue 2: Alert Appears Behind Selfie Screen

Symptoms: Success alert not visible, selfie screen still open

Solution: Ensure parent navigator check and delay are implemented

const selfieScreenWasClosed = closeSelfieScreenIfPresent();

if (selfieScreenWasClosed) {
  // CRITICAL: 300ms delay for screen transition
  setTimeout(() => {
    handleNotificationUpdateResult(data);
  }, 300);
  return;
}

Issue 3: Handler Not Triggering for Workflow 9

Symptoms: Console logs show selfie event but handler not executing

Solution: Check method name (common mistake)

// ✅ Correct method name
idvEventManager.setGetSelfieProcessStartConfirmationHandler(wrappedHandler);

// ❌ Wrong method name (easy to confuse)
idvEventManager.setSelfieProcessStartConfirmationHandler(wrappedHandler);

Issue 4: Other IDV Workflows Broken

Symptoms: Biometric opt-in, KYC flows not working after implementation

Solution: Ensure workflow 9 check doesn't block other workflows

if (data.idvWorkflow === 9) {
  setShowActionModal(false);
  // Only affect workflow 9
} else {
  console.log('Different IDV workflow, ignoring:', data.idvWorkflow);
  // Don't interfere with workflows 10, 6, 8, 15, etc.
}

Let's review important security considerations for server biometric authentication.

Biometric Template Security

Never expose biometric templates:

// ❌ Wrong - Logging biometric data
console.log('Biometric template:', selfieData.template);

// ✅ Correct - Only log workflow and status
console.log('Server biometric verification:', {
  idvWorkflow: data.idvWorkflow,
  success: statusCode === 100
});

Liveness Detection

Trust SDK's liveness detection:

The Aware FaceCapture library embedded in react-native-rdna-client provides:

AuthLevel Enforcement

Never bypass authLevel requirements:

// ❌ Wrong - Trying to bypass server biometric
if (requiresServerBiometric) {
  // Don't call updateNotification again to skip auth
}

// ✅ Correct - Always respect backend authLevel
try {
  await rdnaService.updateNotification(uuid, action);
  // Let SDK handle authLevel=4 requirement via events
} catch (error) {
  // Handle API errors only
}

Compliance and Audit

Log security-relevant events:

// ✅ Correct - Audit logging
console.log('Server biometric step-up initiated:', {
  notificationUUID,
  action,
  timestamp: new Date().toISOString()
});

console.log('Server biometric verification result:', {
  success: statusCode === 100,
  statusCode,
  timestamp: new Date().toISOString()
});

Compliance considerations:

Congratulations! You've successfully implemented server biometric step-up authentication for notification actions with REL-ID SDK.

What You've Accomplished

In this codelab, you've learned how to:

✅ Understand AuthLevel Configuration: Learned how authLevel=4 triggers server biometric (vs challengeMode 3 for password)

✅ Implement IDV Workflow 9: Handled notification-specific selfie capture with liveness detection

✅ Use Dual Handler Registration: Applied useEffect + useFocusEffect pattern for reliable event handling

✅ Chain Event Handlers: Preserved global navigation while adding screen-specific logic (close modal)

✅ Manage Modal Lifecycle: Closed action modal before selfie screen to prevent UI blocking

✅ Use Parent Navigator Pattern: Navigated across drawer/stack hierarchies to close screens smoothly

✅ Implement Screen Transition Timing: Used 300ms delays for smooth UI updates before alerts

✅ Integrate Liveness Detection: Leveraged Aware FaceCapture library for anti-spoofing

Key Takeaways

AuthLevel vs Challenge Mode:

Dual Registration Pattern:

Parent Navigator Pattern:

Handler Chaining:

Additional Resources

Thank you for completing this codelab! You now have the knowledge to implement secure, production-ready server biometric step-up authentication for notification actions in your React Native applications.

Happy Coding! 🚀