🎯 Learning Path:

  1. Complete REL-ID Complete Activation & Login Flow Codelab
  2. Complete REL-ID Biometric Opt-In/Opt-Out Flow Codelab
  3. You are here → Server Biometric Step-Up Authentication for Notification Actions

Welcome to the REL-ID Server Biometric Step-Up Authentication for Notifications codelab! This tutorial teaches you how to implement high-security notification actions that require additional authentication (password, LDA, or server biometric) based on backend configuration.

What You'll Build

In this codelab, you'll implement a notification action system with three authentication levels:

Password Step-Up Authentication (authLevel = 1):

LDA Step-Up Authentication (authLevel = 3):

Server Biometric Step-Up Authentication (authLevel = 4 - PRIMARY FOCUS):

Key Architectural Understanding

CRITICAL: This implementation is fundamentally different from other IDV flows:

❌ OTHER IDV FLOWS (Opt-In, KYC, Additional Document Scan):
- Screen registers getSelfieProcessStartConfirmationHandler
- Screen implements handleSelfieProcessStart() method
- Screen manages navigation to selfie screen
- Screen implements handler chaining patterns

✅ SERVER BIOMETRIC STEP-UP FOR NOTIFICATIONS (Workflow 9):
- SDK automatically detects authLevel=4 from backend
- SDKIDVEventProvider handles ALL selfie navigation
- GetNotificationsScreen ONLY handles onUpdateNotification result event
- NO screen-level IDV event handlers needed
- NO getSelfieProcessStartConfirmationHandler registration
- NO handleSelfieProcessStart() method

What You'll Learn

By completing this codelab, you'll master:

  1. AuthLevel Configuration: Understanding how backend determines authentication method
  2. Workflow 9 Behavior: SDK automatic selfie navigation for notification step-up
  3. StepUpAuthManager Pattern: Context management with callbacks for loading state
  4. Password Dialog Callbacks: onCancel and onSubmit for UI state management
  5. LDA Silent Cancellation: Error code 131 handling without user alerts
  6. Navigate-Back-Before-Alert: SPA pattern for smooth screen transitions
  7. 300ms Delay Pattern: Timing for clean UI updates after navigation
  8. Event-Driven Flow: Asynchronous event handling with updateNotification API
  9. Result Processing: Handle success, error, and critical status codes
  10. Auto-Refresh Pattern: Reload notifications after action completion

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-cordova.git

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

Codelab Architecture Overview

This codelab demonstrates a notification action system with four core components:

  1. GetNotificationsScreen: Main screen that displays notifications and handles action selection
  2. StepUpAuthManager: Centralized manager for password step-up authentication context
  3. StepUpPasswordDialog: Reusable password dialog with notification context display
  4. SDKIDVEventProvider: Global event handler for workflow 9 selfie navigation (SDK-managed)

The screen is intentionally simple because the SDK handles most of the workflow 9 complexity automatically.

Before implementing step-up authentication, let's understand the complete authentication flow and how the backend determines the authentication method.

AuthLevel Configuration Matrix

The backend configures the authLevel for each notification action. The SDK automatically uses the appropriate authentication method:

AuthLevel

Authentication Method

When Used

Implementation Complexity

1

Password

Standard security actions

Medium (dialog with callbacks)

3

LDA (Local Device Auth)

Standard biometric actions

Low (SDK handles internally)

4

Server Biometric

High-security actions requiring liveness

Low (SDK handles automatically)

Complete Event Flow for AuthLevel = 4 (Server Biometric)

Understanding the complete flow is crucial:

1. User Opens GetNotifications Screen
   └─> Screen loads and displays notifications automatically

2. User Taps Notification Card
   └─> Action modal appears with available actions

3. User Selects Action and Taps Action Button
   └─> StepUpAuthManager.setContext() called with notification details and callbacks
   └─> Modal closes
   └─> Loading indicator shown
   └─> rdnaService.updateNotification(notificationUUID, actionValue) called

4. SDK Receives Backend Response with authLevel = 4
   └─> SDK AUTOMATICALLY detects workflow 9 requirement
   └─> SDK checks if user has biometric template (from opt-in)

5. SDK Automatically Triggers Selfie Capture
   └─> SDKIDVEventProvider receives getIDVSelfieProcessStartConfirmation event
   └─> SDKIDVEventProvider AUTOMATICALLY navigates to IDVSelfieProcessStart screen
   └─> NO GetNotificationsScreen involvement needed!

6. User Captures Selfie on Native Screen
   └─> Liveness detection active (Aware FaceCapture)
   └─> SDK sends selfie to server for template matching

7. SDK Receives Verification Result
   └─> SDK triggers onUpdateNotification event
   └─> GetNotificationsScreen.handleUpdateNotificationResponse() executes

8. GetNotificationsScreen Handles Result
   └─> Navigates back to GetNotifications screen
   └─> Waits 300ms for screen transition
   └─> Shows alert with result
   └─> Reloads notifications after alert dismissed

Password Step-Up Flow (authLevel = 1)

For password authentication, the flow is different:

1. User Selects Notification Action
   └─> StepUpAuthManager.setContext() called with callbacks
   └─> rdnaService.updateNotification() called

2. SDK Receives Backend Response with authLevel = 1
   └─> SDK triggers getPassword event with challengeMode = 3
   └─> SDKEventProvider delegates to StepUpAuthManager
   └─> StepUpPasswordDialog.show() displays password dialog

3. User Enters Password
   └─> onSubmit callback fired (keeps loading visible)
   └─> rdnaService.setPassword(password, 3) called

4. SDK Validates Password
   └─> If valid: Processes updateNotification
   └─> If invalid: Shows error, keeps dialog open
   └─> If cancelled: onCancel callback fired (hides loading)

5. SDK Triggers onUpdateNotification Event
   └─> GetNotificationsScreen.handleUpdateNotificationResponse() executes
   └─> Navigate back → Wait 300ms → Show alert → Reload

LDA Step-Up Flow (authLevel = 3)

For LDA authentication:

1. User Selects Notification Action
   └─> rdnaService.updateNotification() called

2. SDK Receives Backend Response with authLevel = 3
   └─> SDK INTERNALLY triggers LDA prompt (Face ID, Touch ID, Fingerprint)
   └─> NO dialog shown by application

3. User Authenticates with Biometric
   └─> If success: SDK processes updateNotification
   └─> If cancelled: SDK returns error code 131 (silent handling)

4. SDK Triggers onUpdateNotification Event
   └─> If error code 131: Navigate back without showing alert
   └─> Otherwise: Navigate back → Wait 300ms → Show alert → Reload

Key Components and Their Responsibilities

Component

Responsibility

Workflow 9 Involvement

GetNotificationsScreen

Display notifications, handle action selection, process results

✅ Handles result only

StepUpAuthManager

Manage password step-up context and callbacks

✅ Password flow only

StepUpPasswordDialog

Display password dialog with notification context

✅ Password flow only

SDKIDVEventProvider

Global handler for workflow 9 selfie navigation

✅ Handles ALL selfie navigation

SDKEventProvider

Global handler for password step-up (challengeMode 3)

✅ Password flow only

Let's set up the project environment and install the REL-ID Cordova plugin.

Install Dependencies

Install required npm packages:

npm install

Install Cordova Plugin (LOCAL)

Add the REL-ID Cordova plugin from the local directory:

cordova plugin add ./RdnaClient

IDV Assets Configuration

The project uses an automated Cordova hook for IDV asset management, but server biometric step-up 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.

What This Project Does NOT Need:

Document Scanner Assets (Not Required):

Key Difference: This codelab demonstrates server biometric step-up authentication for notifications which only uses selfie capture with liveness detection when authLevel=4. It does NOT scan identity documents, so document scanner assets are not required.

What This Project DOES Need:

Biometric Capture Library (Pre-configured):

Optional: Understanding the IDV Asset Hook (For Other Flows)

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 Step-Up Notification Codelab:

Add Platforms

Add iOS and Android platforms:

cordova platform add ios
cordova platform add android

Prepare Project

Prepare the project for both platforms:

cordova prepare

This command:

Run the Application

For iOS:

cordova run ios

For Android:

cordova run android

The StepUpAuthManager is a centralized module that manages password step-up authentication context and callbacks. Let's understand its implementation.

Understanding StepUpAuthManager Architecture

The StepUpAuthManager provides:

  1. Context Storage: Stores notification details for password dialog display
  2. Callback Management: Manages onCancel and onSubmit callbacks for loading state
  3. Password Dialog Control: Shows/hides StepUpPasswordDialog
  4. Password Submission: Handles password verification with challengeMode 3

StepUpAuthManager Context Structure

The context includes notification details and lifecycle callbacks:

// www/src/uniken/managers/StepUpAuthManager.js

/**
 * Step-up authentication context
 * Stores notification details when updateNotification triggers step-up auth
 */
_context: {
  notificationUUID: null,
  notificationTitle: '',
  notificationMessage: '',
  action: null,
  userID: '',
  sessionParams: {},
  onCancel: null,  // Callback when user cancels password dialog
  onSubmit: null   // Callback when user submits password
},

Setting Context with Callbacks

Before calling updateNotification, GetNotificationsScreen sets the context:

// www/src/tutorial/screens/notification/GetNotificationsScreen.js

// Set step-up context in StepUpAuthManager (modular state management)
// SDKEventProvider will delegate to StepUpAuthManager when challengeMode 3 is triggered
if (typeof StepUpAuthManager !== 'undefined') {
  StepUpAuthManager.setContext({
    notificationUUID: notificationUUID,
    notificationTitle: title,
    notificationMessage: message,
    action: actionValue,
    userID: this.sessionParams.userID || '',
    sessionParams: this.sessionParams,

    // Callback when user cancels password dialog
    onCancel: () => {
      console.log('GetNotificationsScreen - Password dialog cancelled by user');
      this.showLoading(false);  // Hide loading indicator
    },

    // Callback when user submits password
    onSubmit: () => {
      console.log('GetNotificationsScreen - Password submitted, waiting for onUpdateNotification event');
      // Keep loading indicator visible, wait for onUpdateNotification event
    }
  });
}

Understanding the Callback Flow

OnCancel Callback Flow:

User Taps Notification Action
└─> updateNotification() called, loading shown
└─> Backend responds with authLevel=1 (password required)
└─> SDK triggers getPassword (challengeMode 3)
└─> StepUpPasswordDialog shows

User Taps "Cancel" in Dialog
└─> StepUpAuthManager.handleCancel() called
└─> onCancel callback fired
└─> this.showLoading(false) executes in GetNotificationsScreen
└─> Loading indicator hidden
└─> Dialog closes
└─> Context cleared

OnSubmit Callback Flow:

User Enters Password and Taps "Submit"
└─> StepUpAuthManager.handlePasswordSubmit() called
└─> onSubmit callback fired
└─> Comment explains: "Keep loading indicator visible"
└─> rdnaService.setPassword(password, 3) called
└─> If valid: SDK processes updateNotification
└─> onUpdateNotification event fires
└─> handleUpdateNotificationResponse() handles result
└─> Loading hidden, dialog hidden, alert shown

StepUpAuthManager Implementation Highlights

setContext Method:

// www/src/uniken/managers/StepUpAuthManager.js

setContext(context) {
  console.log('StepUpAuthManager - Setting context:', JSON.stringify({
    notificationUUID: context.notificationUUID,
    notificationTitle: context.notificationTitle,
    action: context.action,
    userID: context.userID,
    hasOnCancel: !!context.onCancel,
    hasOnSubmit: !!context.onSubmit
  }, null, 2));

  this._context = {
    notificationUUID: context.notificationUUID || null,
    notificationTitle: context.notificationTitle || '',
    notificationMessage: context.notificationMessage || '',
    action: context.action || null,
    userID: context.userID || '',
    sessionParams: context.sessionParams || {},
    onCancel: context.onCancel || null,
    onSubmit: context.onSubmit || null
  };
}

handleCancel Method with Callback:

// www/src/uniken/managers/StepUpAuthManager.js

handleCancel() {
  console.log('StepUpAuthManager - Authentication cancelled');

  // Call onCancel callback if provided (e.g., to hide loading indicator)
  if (this._context.onCancel) {
    console.log('StepUpAuthManager - Calling onCancel callback');
    this._context.onCancel();
  }

  // Clear context
  this.clearContext();

  // Hide modal
  this.hidePasswordDialog();
}

handlePasswordSubmit Method with Callback:

// www/src/uniken/managers/StepUpAuthManager.js

handlePasswordSubmit(password) {
  console.log('StepUpAuthManager - Password submitted');

  // Call onSubmit callback if provided (e.g., to keep loading indicator visible)
  if (this._context.onSubmit) {
    console.log('StepUpAuthManager - Calling onSubmit callback');
    this._context.onSubmit();
  }

  // Update modal to show loading state
  if (typeof StepUpPasswordDialog !== 'undefined') {
    StepUpPasswordDialog.update({ isSubmitting: true });
  }

  // Call setPassword with challengeMode 3
  rdnaService.setPassword(password, 3)
    .then((syncResponse) => {
      console.log('StepUpAuthManager - setPassword sync response:', JSON.stringify({
        longErrorCode: syncResponse.error?.longErrorCode,
        errorString: syncResponse.error?.errorString
      }, null, 2));

      // If setPassword succeeds, SDK will either:
      // 1. Trigger getPassword again if password is wrong (with error status)
      // 2. Process the updateNotification and trigger onUpdateNotification
      // The modal will stay visible until we get the final response
    })
    .catch((error) => {
      console.error('StepUpAuthManager - setPassword sync error:', JSON.stringify(error, null, 2));

      const errorMessage = error?.error?.errorString || 'Failed to verify password';

      // Update modal with error
      if (typeof StepUpPasswordDialog !== 'undefined') {
        StepUpPasswordDialog.update({
          errorMessage: errorMessage,
          isSubmitting: false
        });
      }
    });
}

Now let's implement the result handler that processes the onUpdateNotification event with the navigate-back-before-alert pattern.

Understanding the Navigate-Back-Before-Alert Pattern

This pattern is crucial for smooth UX in Single Page Applications:

CORRECT PATTERN (What We Use):
1. Navigate back to GetNotifications screen
2. Wait 300ms for screen transition to complete
3. Show alert with result
4. Reload notifications after alert dismissed

WHY THIS WORKS:
- Alert appears on correct screen (GetNotifications)
- Screen transition completes before alert shows
- No visual glitches or flickering
- User sees smooth navigation flow

HandleUpdateNotificationResponse Method Structure

The method handles three authentication scenarios:

  1. Password Step-Up (authLevel=1): Result after password validation
  2. LDA Step-Up (authLevel=3): Result after LDA authentication
  3. Server Biometric Step-Up (authLevel=4): Result after workflow 9 selfie verification
// www/src/tutorial/screens/notification/GetNotificationsScreen.js

/**
 * Handle updateNotification response event
 *
 * This handler receives the event after:
 * - Password step-up authentication (challengeMode 3) completes
 * - IDV selfie step-up authentication (idvWorkflow 9) completes
 * - LDA authentication completes
 *
 * Pattern (matches AdditionalDocumentScanScreen):
 * 1. Navigate back to GetNotifications screen
 * 2. Show alert with 300ms delay
 * 3. Refresh notifications after alert
 */
handleUpdateNotificationResponse(data) {
  console.log('GetNotificationsScreen - Processing update notification response');

  this.showLoading(false);

  // Clear step-up context and hide modal via StepUpAuthManager
  if (typeof StepUpAuthManager !== 'undefined') {
    StepUpAuthManager.clearContext();
    StepUpAuthManager.hidePasswordDialog();
  }

  const errorCode = data.error?.longErrorCode;
  const statusCode = data.pArgs?.response?.StatusCode;
  const statusMsg = data.pArgs?.response?.StatusMsg || 'Unknown error';
  const errorString = data.error?.errorString || 'Failed to update notification';

  // FIRST: Check error code (API-level error, including LDA cancelled)
  if (errorCode !== 0) {
    console.error('GetNotificationsScreen - API error:', errorCode, errorString);

    // Handle LDA cancelled (errorCode 131) - Silent return
    if (errorCode === 131) {
      console.log('GetNotificationsScreen - LDA cancelled by user, silent return');
      // Navigate back to GetNotifications without showing error
      NavigationService.navigate('GetNotifications', this.sessionParams);
      return;
    }

    // Navigate back to GetNotifications screen
    NavigationService.navigate('GetNotifications', this.sessionParams);

    // Show error alert after navigation
    setTimeout(() => {
      alert(`Notification Action Failed\n\nUnable to complete notification action.\n\n${errorString}\n\nError Code: ${errorCode}`);
      this.loadNotifications(); // Refresh after alert
    }, 300);

    return;
  }

  // SECOND: Check status code (backend validation)
  if (statusCode === 100) {
    // Success case
    console.log('GetNotificationsScreen - Notification updated successfully:', statusMsg);

    // Navigate back to GetNotifications screen
    NavigationService.navigate('GetNotifications', this.sessionParams);

    // Show success alert after navigation
    setTimeout(() => {
      alert(statusMsg || 'Notification action completed successfully');
      this.loadNotifications(); // Refresh after alert
    }, 300);

  } else if (statusCode === 110 || statusCode === 153) {
    // Critical errors: Password expired (110) or Attempts exhausted (153)
    console.warn('GetNotificationsScreen - Critical error, user will be logged out:', statusCode);

    // Navigate back to GetNotifications screen
    NavigationService.navigate('GetNotifications', this.sessionParams);

    // Show critical error alert
    setTimeout(() => {
      alert(`Authentication Failed\n\n${statusMsg}\n\nYou will be logged out.`);
    }, 300);

    // SDK will automatically trigger onUserLoggedOff → getUser
  } else {
    console.error('GetNotificationsScreen - StatusCode error:', statusCode, statusMsg);

    // Navigate back to GetNotifications screen
    NavigationService.navigate('GetNotifications', this.sessionParams);

    // Show error alert
    setTimeout(() => {
      alert(`Notification Action Failed\n\n${statusMsg}\n\nStatus Code: ${statusCode}`);
      this.loadNotifications(); // Refresh after alert
    }, 300);
  }
}

Key Implementation Details

1. Silent LDA Cancellation (Error Code 131):

// Handle LDA cancelled (errorCode 131) - Silent return
if (errorCode === 131) {
  console.log('GetNotificationsScreen - LDA cancelled by user, silent return');
  // Navigate back to GetNotifications without showing error
  NavigationService.navigate('GetNotifications', this.sessionParams);
  return; // NO alert shown
}

2. Navigate-Back-Before-Alert Pattern:

// Navigate back to GetNotifications screen
NavigationService.navigate('GetNotifications', this.sessionParams);

// Show success alert after navigation
setTimeout(() => {
  alert(statusMsg || 'Notification action completed successfully');
  this.loadNotifications(); // Refresh after alert
}, 300);

3. Auto-Refresh After Alert:

setTimeout(() => {
  alert(statusMsg || 'Notification action completed successfully');
  this.loadNotifications(); // Refresh notifications list
}, 300);

4. Critical Status Code Handling:

} else if (statusCode === 110 || statusCode === 153) {
  // Critical errors: Password expired (110) or Attempts exhausted (153)
  console.warn('GetNotificationsScreen - Critical error, user will be logged out:', statusCode);

  // Navigate back to GetNotifications screen
  NavigationService.navigate('GetNotifications', this.sessionParams);

  // Show critical error alert
  setTimeout(() => {
    alert(`Authentication Failed\n\n${statusMsg}\n\nYou will be logged out.`);
  }, 300);

  // SDK will automatically trigger onUserLoggedOff → getUser
}

Why This Handler is Simple

Notice that this handler does NOT include:

Why?

Now let's implement the action selection flow that sets context and calls the updateNotification API.

Understanding the Action Flow

The action selection flow follows this sequence:

1. User Taps Notification Card
   └─> showActionModal(notification) displays modal

2. User Taps Action Button (e.g., "Approve", "Reject")
   └─> handleActionButtonClick(actionValue) executes

3. Set StepUpAuthManager Context with Callbacks
   └─> Context includes notification details
   └─> onCancel callback to hide loading
   └─> onSubmit callback to track password submission

4. Close Modal and Show Loading
   └─> Modal closes
   └─> Loading indicator shows

5. Call updateNotification API
   └─> rdnaService.updateNotification(notificationUUID, actionValue)

6. Backend Responds with AuthLevel
   └─> authLevel=1: SDK triggers getPassword (challengeMode 3)
   └─> authLevel=3: SDK triggers LDA internally
   └─> authLevel=4: SDK triggers workflow 9 selfie capture

7. Authentication Completes
   └─> onUpdateNotification event fires
   └─> handleUpdateNotificationResponse() processes result

Implementing handleActionButtonClick Method

This method is called when the user taps an action button in the modal:

// www/src/tutorial/screens/notification/GetNotificationsScreen.js

/**
 * Handle action button click (React Native style - direct action)
 */
handleActionButtonClick(actionValue) {
  if (!this.currentNotification || !actionValue) {
    console.error('GetNotificationsScreen - Invalid action or notification');
    return;
  }

  // Store UUID BEFORE closing modal (closeActionModal sets currentNotification to null!)
  const notificationUUID = this.currentNotification.notification_uuid;
  const title = this.currentNotification.body?.[0]?.subject || 'Notification Action';
  const message = this.currentNotification.body?.[0]?.message || '';

  console.log('GetNotificationsScreen - Action button clicked:', actionValue);
  console.log('GetNotificationsScreen - Notification UUID:', notificationUUID);

  // Set step-up context in StepUpAuthManager (modular state management)
  // SDKEventProvider will delegate to StepUpAuthManager when challengeMode 3 is triggered
  if (typeof StepUpAuthManager !== 'undefined') {
    StepUpAuthManager.setContext({
      notificationUUID: notificationUUID,
      notificationTitle: title,
      notificationMessage: message,
      action: actionValue,
      userID: this.sessionParams.userID || '',
      sessionParams: this.sessionParams,

      // Callback when user cancels password dialog
      onCancel: () => {
        console.log('GetNotificationsScreen - Password dialog cancelled by user');
        this.showLoading(false);  // Hide loading indicator
      },

      // Callback when user submits password
      onSubmit: () => {
        console.log('GetNotificationsScreen - Password submitted, waiting for onUpdateNotification event');
        // Keep loading indicator visible, wait for onUpdateNotification event
      }
    });
  }

  // Close modal
  this.closeActionModal();

  // Show loading
  this.showLoading(true);

  // Call SDK updateNotification API
  rdnaService.updateNotification(notificationUUID, actionValue)
    .then((syncResponse) => {
      console.log('GetNotificationsScreen - UpdateNotification sync response:', JSON.stringify(syncResponse, null, 2));
      // Waiting for onUpdateNotification event
      // If step-up auth is required, SDK will trigger getPassword with challengeMode 3
      // SDKEventProvider will handle it and show StepUpPasswordDialog
    })
    .catch((error) => {
      console.error('GetNotificationsScreen - UpdateNotification error:', JSON.stringify(error, null, 2));
      this.showLoading(false);
      this.showError('Failed to update notification. Please try again.');

      // Clear step-up context on error
      if (typeof StepUpAuthManager !== 'undefined') {
        StepUpAuthManager.clearContext();
      }
    });
}

Key Implementation Details

1. Store Notification Data Before Closing Modal:

// Store UUID BEFORE closing modal (closeActionModal sets currentNotification to null!)
const notificationUUID = this.currentNotification.notification_uuid;
const title = this.currentNotification.body?.[0]?.subject || 'Notification Action';
const message = this.currentNotification.body?.[0]?.message || '';

2. Set Context with Callbacks:

StepUpAuthManager.setContext({
  notificationUUID: notificationUUID,
  notificationTitle: title,
  notificationMessage: message,
  action: actionValue,
  userID: this.sessionParams.userID || '',
  sessionParams: this.sessionParams,

  // Callback when user cancels password dialog
  onCancel: () => {
    console.log('GetNotificationsScreen - Password dialog cancelled by user');
    this.showLoading(false);  // Hide loading indicator
  },

  // Callback when user submits password
  onSubmit: () => {
    console.log('GetNotificationsScreen - Password submitted, waiting for onUpdateNotification event');
    // Keep loading indicator visible, wait for onUpdateNotification event
  }
});

3. Close Modal and Show Loading:

// Close modal
this.closeActionModal();

// Show loading
this.showLoading(true);

4. Call updateNotification API:

rdnaService.updateNotification(notificationUUID, actionValue)
  .then((syncResponse) => {
    console.log('GetNotificationsScreen - UpdateNotification sync response:', JSON.stringify(syncResponse, null, 2));
    // Waiting for onUpdateNotification event
    // If step-up auth is required, SDK will trigger getPassword with challengeMode 3
    // SDKEventProvider will handle it and show StepUpPasswordDialog
  })
  .catch((error) => {
    console.error('GetNotificationsScreen - UpdateNotification error:', JSON.stringify(error, null, 2));
    this.showLoading(false);
    this.showError('Failed to update notification. Please try again.');

    // Clear step-up context on error
    if (typeof StepUpAuthManager !== 'undefined') {
      StepUpAuthManager.clearContext();
    }
  });

5. Error Handling:

.catch((error) => {
  console.error('GetNotificationsScreen - UpdateNotification error:', JSON.stringify(error, null, 2));
  this.showLoading(false);
  this.showError('Failed to update notification. Please try again.');

  // Clear step-up context on error
  if (typeof StepUpAuthManager !== 'undefined') {
    StepUpAuthManager.clearContext();
  }
});

showActionModal Method (For Context)

This method displays the action modal when user taps a notification card:

// www/src/tutorial/screens/notification/GetNotificationsScreen.js

/**
 * Show action modal for notification (matches React Native UI)
 */
showActionModal(notification) {
  console.log('GetNotificationsScreen - Opening action modal for notification:', notification.notification_uuid);

  this.currentNotification = notification;

  // Update modal content
  const modalTitle = document.getElementById('notification-modal-title');
  const modalBody = document.getElementById('notification-modal-body');

  // Extract notification data (Cordova plugin structure)
  const title = notification.body?.[0]?.subject || 'Notification';
  const message = notification.body?.[0]?.message || '';
  const createdTimestamp = notification.create_ts || '';
  const expiryTimestamp = notification.expiry_timestamp || '';
  const uuid = notification.notification_uuid || '';
  const actions = notification.actions || [];

  // Set modal title
  if (modalTitle) modalTitle.textContent = 'Notification Actions';

  // Build modal body with full details + action buttons (React Native style)
  if (modalBody) {
    modalBody.innerHTML = `
      <div class="notification-details">
        <h3 class="detail-title">${title}</h3>
        <p class="detail-message">${message.replace(/<br\/>/g, '<br>')}</p>
        <p class="detail-time">Created: ${this.formatTimestamp(createdTimestamp)}</p>
        ${expiryTimestamp ? `<p class="detail-time">Expires: ${this.formatTimestamp(expiryTimestamp)}</p>` : ''}
        <p class="detail-uuid">ID: ${uuid}</p>
      </div>
      <div class="action-buttons-container">
        ${actions.map(action => `
          <button class="action-button" data-action="${action.action}">
            ${action.label}
          </button>
        `).join('')}
      </div>
    `;

    // Attach click handlers to action buttons
    const actionButtons = modalBody.querySelectorAll('.action-button');
    actionButtons.forEach(btn => {
      btn.onclick = () => this.handleActionButtonClick(btn.getAttribute('data-action'));
    });
  }

  // Show modal
  const modal = document.getElementById('notification-action-modal');
  if (modal) modal.style.display = 'flex';
}

Now let's implement the event handler registration that connects SDK events to our screen methods.

Understanding Event Handler Registration

GetNotificationsScreen registers TWO event handlers:

  1. getNotificationsHandler: Processes notification list from getNotifications() API
  2. updateNotificationHandler: Processes result from updateNotification() API (after authentication)

CRITICAL: GetNotificationsScreen does NOT register:

Implementing registerSDKEventHandlers Method

// www/src/tutorial/screens/notification/GetNotificationsScreen.js

/**
 * Register SDK event handlers for notifications
 */
registerSDKEventHandlers() {
  const eventManager = rdnaService.getEventManager();

  // Handle getNotifications response
  eventManager.setGetNotificationsHandler((data) => {
    console.log('GetNotificationsScreen - onGetNotifications event received');
    this.handleGetNotificationsResponse(data);
  });

  // Handle updateNotification response
  eventManager.setUpdateNotificationHandler((data) => {
    console.log('GetNotificationsScreen - onUpdateNotification event received');
    this.handleUpdateNotificationResponse(data);
  });

  // Note: getPassword (challengeMode 3) is handled centrally by SDKEventProvider
  // We just need to set context before calling updateNotification

  console.log('GetNotificationsScreen - SDK event handlers registered');
}

Key Points About Event Registration

1. Handler Binding:

eventManager.setGetNotificationsHandler((data) => {
  console.log('GetNotificationsScreen - onGetNotifications event received');
  this.handleGetNotificationsResponse(data);
});

2. Centralized Password Handling:

// Note: getPassword (challengeMode 3) is handled centrally by SDKEventProvider
// We just need to set context before calling updateNotification

3. No IDV Event Handlers:

Notice we do NOT register:

// ❌ NOT NEEDED for workflow 9:
// eventManager.setGetSelfieProcessStartConfirmationHandler(...)
// eventManager.setIDVOptInCapturedFrameConfirmationHandler(...)
// eventManager.setIDVOptInStatusHandler(...)

Calling registerSDKEventHandlers

The method is called during screen initialization:

// www/src/tutorial/screens/notification/GetNotificationsScreen.js

/**
 * Called when screen content is loaded (SPA lifecycle)
 *
 * @param {Object} params - Navigation parameters
 */
onContentLoaded(params) {
  console.log('GetNotificationsScreen - Content loaded');

  // Store session params for navigation back to Dashboard
  this.sessionParams = params || {};

  // Store session params globally for use across screens (e.g., UpdatePassword)
  if (typeof SDKEventProvider !== 'undefined') {
    SDKEventProvider.setSessionParams(this.sessionParams);
  }

  // Setup UI
  this.setupEventListeners();
  this.registerSDKEventHandlers();  // Register SDK event handlers

  // Auto-load notifications
  this.loadNotifications();
}

Now let's test all three authentication scenarios: password, LDA, and server biometric step-up.

Prerequisites for Testing

Before testing, ensure:

  1. User is logged in - Complete MFA activation and login
  2. Notifications exist - Backend has created test notifications with different authLevels
  3. For authLevel=4: User has completed biometric opt-in (template exists on server)

Test Scenario 1: Password Step-Up (authLevel = 1)

Test password authentication for notification actions:

Setup: Backend configures notification action with authLevel = 1

Test Steps:

  1. Navigate to GetNotifications - Open drawer, tap "Get Notifications"
  2. Wait for notifications to load - Screen auto-loads on entry
  3. Verify notification display:
    Console logs:
    - "GetNotificationsScreen - Loading notifications from server"
    - "GetNotificationsScreen - onGetNotifications event received"
    - "GetNotificationsScreen - Received X notifications"
    - "GetNotificationsScreen - Rendered X notifications"
    
  4. Tap notification card - Opens action modal
  5. Verify modal content:
    • Title: "Notification Actions"
    • Notification details: subject, message, timestamps, UUID
    • Action buttons displayed (e.g., "Approve", "Reject")
  6. Tap action button (e.g., "Approve")
  7. Verify StepUpAuthManager context set:
    Console logs:
    - "GetNotificationsScreen - Action button clicked: Approve"
    - "StepUpAuthManager - Setting context"
    - "GetNotificationsScreen - UpdateNotification sync response"
    
  8. Verify password dialog appears:
    • Title shows notification subject
    • Message shows notification message
    • User ID displayed
    • Password input field
    • "Submit" and "Cancel" buttons visible
  9. Test Cancel Flow:
    • Tap "Cancel" button
    • Verify onCancel callback fires:
      Console logs:
      - "GetNotificationsScreen - Password dialog cancelled by user"
      
    • Verify loading indicator hidden
    • Verify dialog closed
    • Verify context cleared
  10. Retry and Test Submit Flow:
    • Tap same action button again
    • Enter password
    • Tap "Submit"
    • Verify onSubmit callback fires:
      Console logs:
      - "GetNotificationsScreen - Password submitted, waiting for onUpdateNotification event"
      
    • Verify loading indicator REMAINS visible
    • Verify dialog shows "Submitting..." state
  11. Verify authentication result:
    Console logs:
    - "GetNotificationsScreen - onUpdateNotification event received"
    - "GetNotificationsScreen - Processing update notification response"
    - "GetNotificationsScreen - Notification updated successfully"
    - "GetNotificationsScreen - Navigating back to GetNotifications screen"
    
  12. Verify navigate-back-before-alert pattern:
    • Screen navigates back to GetNotifications
    • 300ms delay
    • Alert appears: "Notification action completed successfully"
  13. Tap "OK" on alert
  14. Verify auto-refresh:
    Console logs:
    - "GetNotificationsScreen - Loading notifications from server"
    
  15. Verify UI updates - Notification list refreshes with updated status

Verification Checklist:

Test Scenario 2: LDA Step-Up (authLevel = 3)

Test LDA authentication for notification actions:

Setup: Backend configures notification action with authLevel = 3

Test Steps:

  1. Navigate to GetNotifications
  2. Tap notification card - Opens action modal
  3. Tap action button - Triggers updateNotification
  4. Verify LDA prompt appears - Native biometric prompt (Face ID, Touch ID, Fingerprint)
  5. Test Successful LDA:
    • Authenticate with biometric
    • Verify result handling:
      Console logs:
      - "GetNotificationsScreen - onUpdateNotification event received"
      - "GetNotificationsScreen - Notification updated successfully"
      
    • Navigate back → 300ms delay → Alert → Auto-refresh
  6. Test LDA Cancellation:
    • Repeat action selection
    • Cancel LDA prompt
    • Verify silent cancellation:
      Console logs:
      - "GetNotificationsScreen - API error: 131"
      - "GetNotificationsScreen - LDA cancelled by user, silent return"
      
    • Verify NO alert shown
    • Verify navigation back to GetNotifications
    • Verify NO notifications refresh (action not completed)

Verification Checklist:

Test Scenario 3: Server Biometric Step-Up (authLevel = 4)

Test server biometric authentication for notification actions (PRIMARY FOCUS):

Setup:

Test Steps:

  1. Navigate to GetNotifications
  2. Tap notification card - Opens action modal
  3. Tap action button - Triggers updateNotification
  4. Verify SDK automatic workflow detection:
    Console logs:
    - "GetNotificationsScreen - Action button clicked"
    - "GetNotificationsScreen - UpdateNotification sync response"
    - "SDKIDVEventProvider - getIDVSelfieProcessStartConfirmation event received"
    - "SDKIDVEventProvider - idvWorkflow: 9"
    - "SDKIDVEventProvider - Navigating to IDVSelfieProcessStart screen"
    
  5. Verify automatic navigation to selfie screen:
    • Screen transitions to IDVSelfieProcessStart
    • NO GetNotificationsScreen involvement in navigation
    • Native camera UI appears
  6. Complete selfie capture:
    • Follow liveness detection prompts
    • Capture selfie with liveness verification
  7. Verify SDK sends selfie to server:
    Console logs:
    - "Processing selfie..."
    - "Verifying biometric template..."
    
  8. Verify result handling:
    Console logs:
    - "GetNotificationsScreen - onUpdateNotification event received"
    - "GetNotificationsScreen - Processing update notification response"
    - "GetNotificationsScreen - Notification updated successfully"
    - "GetNotificationsScreen - Navigating back to GetNotifications screen"
    
  9. Verify navigate-back-before-alert pattern:
    • Screen navigates back to GetNotifications
    • 300ms delay
    • Alert appears with success message
  10. Tap "OK" on alert
  11. Verify auto-refresh - Notifications list reloads

Verification Checklist:

Test Scenario 4: Critical Status Codes

Test password expiry and attempt exhaustion:

Test Steps (Password Expiry - Status Code 110):

  1. Configure backend - Set user password to expired state
  2. Trigger notification action - With authLevel=1 (password)
  3. Enter password - Submit valid password
  4. Verify critical error handling:
    Console logs:
    - "GetNotificationsScreen - StatusCode error: 110"
    - "GetNotificationsScreen - Critical error, user will be logged out: 110"
    
  5. Verify navigate-back-before-alert pattern:
    • Navigate back to GetNotifications
    • 300ms delay
    • Alert: "Authentication Failed\n\nPassword has expired\n\nYou will be logged out."
  6. Tap "OK" on alert
  7. Verify auto-logout:
    • SDK triggers onUserLoggedOff event
    • User redirected to CheckUser screen

Test Steps (Attempts Exhausted - Status Code 153):

  1. Trigger notification action - With authLevel=1 (password)
  2. Enter wrong password - Multiple times until attempts exhausted
  3. Verify critical error handling:
    Console logs:
    - "GetNotificationsScreen - StatusCode error: 153"
    - "GetNotificationsScreen - Critical error, user will be logged out: 153"
    
  4. Verify navigate-back-before-alert pattern
  5. Verify auto-logout after alert dismissed

Verification Checklist:

Common Testing Issues and Solutions

Issue 1: Password Dialog Not Appearing

Symptoms: Notification action triggered, but no password dialog shows

Solution:

// Verify StepUpAuthManager context is set BEFORE updateNotification
// Check console logs:
console.log('StepUpAuthManager - Setting context'); // Should appear BEFORE updateNotification

// Verify SDKEventProvider is handling getPassword event
console.log('SDKEventProvider - getPassword event received, challengeMode: 3');

Issue 2: Loading Indicator Stuck After Cancel

Symptoms: User cancels password dialog, but loading remains visible

Solution:

// Verify onCancel callback is set and called
onCancel: () => {
  console.log('GetNotificationsScreen - Password dialog cancelled by user');
  this.showLoading(false);  // This MUST be called
}

Issue 3: Alert Appears During Screen Transition

Symptoms: Alert shows while screen is still transitioning, causing visual glitch

Solution:

// Verify 300ms delay is present
NavigationService.navigate('GetNotifications', this.sessionParams);

// MUST have setTimeout with 300ms delay
setTimeout(() => {
  alert(statusMsg || 'Notification action completed successfully');
  this.loadNotifications();
}, 300); // CRITICAL: 300ms delay

Issue 4: Selfie Screen Not Appearing (authLevel=4)

Symptoms: UpdateNotification called with authLevel=4, but selfie screen doesn't appear

Solution:

// Verify user has completed biometric opt-in
// Check template status before testing

// Verify SDKIDVEventProvider is handling getIDVSelfieProcessStartConfirmation
// Check console logs:
console.log('SDKIDVEventProvider - getIDVSelfieProcessStartConfirmation event received');
console.log('SDKIDVEventProvider - idvWorkflow: 9');
console.log('SDKIDVEventProvider - Navigating to IDVSelfieProcessStart screen');

Issue 5: Notifications Not Refreshing After Success

Symptoms: Alert shows, but notification list doesn't update

Solution:

// Verify loadNotifications() is called after alert
setTimeout(() => {
  alert(statusMsg || 'Notification action completed successfully');
  this.loadNotifications(); // MUST be called
}, 300);

Let's understand the key architectural decisions and design patterns used in this implementation.

Why Workflow 9 is Different from Other IDV Flows

Other IDV Flows (Opt-In, KYC, Additional Document Scan):

Screen registers getSelfieProcessStartConfirmationHandler
       ↓
Screen implements handleSelfieProcessStart() method
       ↓
Screen detects idvWorkflow parameter
       ↓
Screen navigates to IDVSelfieProcessStart screen
       ↓
Screen handles result and shows modals

Workflow 9 (Notification Step-Up):

GetNotificationsScreen calls updateNotification()
       ↓
SDK detects authLevel=4 from backend
       ↓
SDKIDVEventProvider receives getIDVSelfieProcessStartConfirmation event
       ↓
SDKIDVEventProvider automatically navigates to IDVSelfieProcessStart
       ↓
User completes selfie capture
       ↓
SDK triggers onUpdateNotification event
       ↓
GetNotificationsScreen handles result only

Modular State Management with StepUpAuthManager

Design Goal: Keep GetNotificationsScreen focused on notification display and action selection, delegate password step-up logic to a separate manager.

Benefits:

  1. Separation of Concerns: GetNotificationsScreen doesn't need to know password dialog internals
  2. Reusability: StepUpAuthManager can be used by other screens (e.g., data signing, device management)
  3. Callback Pattern: Loose coupling via callbacks instead of tight coupling
  4. Centralized Logic: SDKEventProvider delegates to StepUpAuthManager for all challengeMode 3 events

Pattern:

GetNotificationsScreen (Context Provider)
       ↓ setContext()
StepUpAuthManager (Context Storage + Password Logic)
       ↑ onCancel/onSubmit callbacks
GetNotificationsScreen (Loading State Management)

Navigate-Back-Before-Alert Pattern for SPA

Why This Pattern Exists:

In Single Page Applications, modals and alerts can appear on the wrong screen if navigation timing is incorrect. This pattern ensures alerts appear on the correct screen after smooth transitions.

Pattern:

// STEP 1: Navigate back to target screen
NavigationService.navigate('GetNotifications', this.sessionParams);

// STEP 2: Wait 300ms for screen transition animation
setTimeout(() => {
  // STEP 3: Show alert (now on correct screen)
  alert(statusMsg || 'Notification action completed successfully');

  // STEP 4: Refresh data after alert dismissed
  this.loadNotifications();
}, 300);

Timing Breakdown:

Silent LDA Cancellation (Error Code 131)

Design Goal: When users cancel LDA authentication (Face ID, Touch ID, Fingerprint), they intentionally chose not to proceed. Showing an error alert would be redundant and annoying.

Pattern:

// Check for LDA cancellation (error code 131)
if (errorCode === 131) {
  console.log('GetNotificationsScreen - LDA cancelled by user, silent return');
  // Navigate back WITHOUT showing alert
  NavigationService.navigate('GetNotifications', this.sessionParams);
  return; // NO alert, NO error message
}

User Experience:

User Taps Notification Action
       ↓
Native LDA Prompt Appears (Face ID)
       ↓
User Taps "Cancel" on Face ID Prompt
       ↓
Screen Returns to GetNotifications (Silent)
       ↓
User Can Retry or Choose Different Action

Event-Driven Architecture with Async Result Handling

REL-ID SDK Pattern:

All SDK APIs follow the sync response + async event pattern:

1. Call SDK API (e.g., updateNotification)
       ↓
2. Receive SYNC response (validates request format)
       ↓
3. SDK processes request (authentication, backend communication)
       ↓
4. SDK triggers ASYNC event (e.g., onUpdateNotification)
       ↓
5. Event handler processes result

Example:

// SYNC: Immediate response
rdnaService.updateNotification(notificationUUID, actionValue)
  .then((syncResponse) => {
    console.log('UpdateNotification sync response:', syncResponse);
    // syncResponse only validates request was accepted
    // DOES NOT contain final result
  });

// ASYNC: Final result via event
eventManager.setUpdateNotificationHandler((data) => {
  console.log('onUpdateNotification event received');
  // data contains final result after authentication
  this.handleUpdateNotificationResponse(data);
});

Callback Pattern for Loading State Management

Design Goal: Allow StepUpAuthManager to notify GetNotificationsScreen about password dialog lifecycle events without tight coupling.

Pattern:

// GetNotificationsScreen sets callbacks
StepUpAuthManager.setContext({
  // ... notification data ...

  onCancel: () => {
    this.showLoading(false);  // Hide loading when user cancels
  },

  onSubmit: () => {
    // Keep loading visible when user submits password
    // Wait for onUpdateNotification event
  }
});

// StepUpAuthManager calls callbacks at appropriate times
handleCancel() {
  if (this._context.onCancel) {
    this._context.onCancel(); // Notify screen of cancellation
  }
  this.clearContext();
  this.hidePasswordDialog();
}

handlePasswordSubmit(password) {
  if (this._context.onSubmit) {
    this._context.onSubmit(); // Notify screen of submission
  }
  // Continue with password validation
}

Benefits:

Let's review important security considerations for notification step-up authentication.

Never Log Sensitive Data

Avoid logging:

// ❌ Wrong - Logging password
console.log('Password submitted:', password);

// ❌ Wrong - Logging full notification data (may contain PII)
console.log('Notification data:', JSON.stringify(notification));

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

Safe logging:

// ✅ Correct - Log only non-sensitive metadata
console.log('Password submitted, waiting for validation');

// ✅ Correct - Log only notification UUID
console.log('Notification UUID:', notification.notification_uuid);

// ✅ Correct - Log only status codes
console.log('Authentication result:', {
  statusCode: data.status?.statusCode,
  errorCode: data.error?.longErrorCode
});

Validate Authentication Requirements

Always respect backend authLevel configuration:

// ✅ Correct - Let SDK handle authentication based on backend config
rdnaService.updateNotification(notificationUUID, actionValue)
  .then(() => {
    // SDK will automatically trigger appropriate authentication:
    // authLevel=1: Password (challengeMode 3)
    // authLevel=3: LDA (internal)
    // authLevel=4: Server Biometric (workflow 9)
  });

// ❌ Wrong - Hardcoding authentication method
// Always show password dialog regardless of authLevel
StepUpPasswordDialog.show(...);

Handle Critical Status Codes Properly

Critical status codes require immediate action:

// Status Code 110: Password Expired
// Status Code 153: Attempts Exhausted

if (statusCode === 110 || statusCode === 153) {
  console.warn('Critical error, user will be logged out:', statusCode);

  // Show warning alert
  alert(`Authentication Failed\n\n${statusMsg}\n\nYou will be logged out.`);

  // SDK will automatically log out user
  // DO NOT try to prevent logout
}

Protect Against Template Manipulation

For authLevel=4 scenarios:

// ✅ Correct - SDK validates template on server
// Template matching happens on REL-ID server
// Client cannot manipulate biometric data

// ❌ Wrong - Client-side template validation
// Never trust client-side biometric validation
// Always use server-side validation (authLevel=4)

Secure Context Management

Clear context after use:

// ✅ Correct - Clear context after authentication completes
handleUpdateNotificationResponse(data) {
  // ... process result ...

  // Clear context immediately
  if (typeof StepUpAuthManager !== 'undefined') {
    StepUpAuthManager.clearContext();
    StepUpAuthManager.hidePasswordDialog();
  }
}

// ❌ Wrong - Leaving context set indefinitely
// Context contains notification details and callbacks
// Clear it to prevent memory leaks and security issues

LDA Security Considerations

Respect platform biometric policies:

// ✅ Correct - Let SDK handle LDA internally
// SDK respects platform security policies:
// - iOS: Face ID, Touch ID enrollment requirements
// - Android: Fingerprint, Face authentication policies

// ❌ Wrong - Bypassing LDA
// Never provide a way to skip LDA when authLevel=3
if (errorCode === 131) {
  // User cancelled LDA - silent return
  // DO NOT offer alternative authentication without backend approval
  return;
}

Congratulations! You've successfully implemented server biometric step-up authentication for notifications with REL-ID SDK in Cordova.

What You've Accomplished

In this codelab, you've learned how to:

✅ Understand AuthLevel Configuration: Learned how backend determines authentication method (password, LDA, server biometric)

✅ Implement StepUpAuthManager: Built centralized manager for password step-up with callback pattern

✅ Use Callback Pattern: Implemented onCancel and onSubmit callbacks for loading state management

✅ Handle Workflow 9 Automatically: Understood that SDK handles selfie navigation via SDKIDVEventProvider

✅ Implement Navigate-Back-Before-Alert: Applied SPA pattern for smooth screen transitions with 300ms delay

✅ Handle LDA Silent Cancellation: Processed error code 131 without showing alert for better UX

✅ Process Result Events: Handled onUpdateNotification event with success, error, and critical status codes

✅ Auto-Refresh Notifications: Reloaded notification list after actions complete

✅ Distinguish Workflow 9: Recognized that notification step-up is simpler than other IDV flows

Key Takeaways

Workflow 9 is SDK-Driven:

Modular State Management:

Navigate-Back-Before-Alert Pattern:

Silent LDA Cancellation:

AuthLevel Configuration:

Comparison with Other IDV Flows

Aspect

Workflow 9 (Notification Step-Up)

Other IDV Flows (Opt-In, KYC)

Selfie Navigation

SDK automatic (SDKIDVEventProvider)

Screen-driven (getSelfieProcessStartConfirmationHandler)

Screen Complexity

Low (only handles result)

High (manages entire flow)

Event Handlers

2 (getNotifications, updateNotification)

4+ (selfie start, frame confirmation, status, etc.)

Handler Chaining

Not needed

Required to preserve global handlers

Navigation Logic

SDK-managed

Screen-managed

Implementation Effort

Low

Medium-High

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 Cordova applications.

Happy Coding!