Orchestration SDKs

Step 2. Integrating PingOne Protect DaVinci flows in React Native

PingOne React Native

Integrating your application with PingOne Protect enables you to perform risk evaluations during your customer’s authentication flow.

The PingOne Protect module runs silently in the background; it requires no user interaction.

Initializing PingOne Protect data collection

The earlier you can initialize data collection, the more data it can collect to make a risk evaluation.

There are two main methods for initializing data collection in a React Native app:

Manual initialization

Initialize the PingOne Protect module by calling startProtect() with a configuration object:

Initializing the PingOne Protect module at app startup
import { startProtect } from '@ping-identity/rn-protect';

await startProtect({
  envId: "3072206d-c6ce-ch15-m0nd-f87e972c7cc3",
  isBehavioralDataCollection: true,
  resumeBehavioralDataOnStart: true,
});

The available configuration properties are as follows:

Parameter Description

envId

Required. Your PingOne environment identifier.

For example, 3072206d-c6ce-ch15-m0nd-f87e972c7cc3

deviceAttributesToIgnore

Optional. A list of device attributes to ignore when collecting device signals.

For example, AUDIO_OUTPUT_DEVICES or IS_ACCEPT_COOKIES.

isBehavioralDataCollection

When true, collect behavioral data.

Default is true.

isConsoleLogEnabled

When true, output SDK log messages in the developer console.

Default is false.

isLazyMetadata

When true, calculate metadata on demand rather than automatically after calling start.

Default is false.

customHost

Optional. Specify a custom host for the Protect API, which can be useful in specific deployment scenarios.

pauseBehavioralDataOnSuccess

When true, the DaVinci Client automatically pauses behavioral data collection after successful authentication.

Default is false.

On React Native, this property only takes effect when startProtect() is called before the DaVinci client is created.

In that order, the module injects a lifecycle module into the native client, which pauses behavioral data collection when the flow succeeds. If the client was created first, the property has no effect, and you must call pauseBehavioralData() manually after the flow succeeds.

resumeBehavioralDataOnStart

When true, the DaVinci Client automatically calls resumeBehavioralData() on your behalf, either when the module initializes or when a flow starts.

Call startProtect() before creating the DaVinci client.

When startProtect() runs first, it registers a lifecycle module with the native layer, and that module is injected into the DaVinci client when the client is created. This is what enables the pauseBehavioralDataOnSuccess and resumeBehavioralDataOnStart properties.

If you create the client first, the client does not include the lifecycle module, and those properties have no effect.

On-demand initialization

You can choose not to initialize data collection on app startup and instead initialize it when your DaVinci flow reaches a risk evaluation step.

Deferring initialization means your app only collects behavioral data in flows that actually perform PingOne Protect risk evaluations, because behavioral data collection runs continuously once initialized.

On-demand initialization is incompatible with the pauseBehavioralDataOnSuccess and resumeBehavioralDataOnStart properties if the DaVinci client is created before startProtect() runs, because the lifecycle module is injected into the client only when the client is created after startProtect().

If you initialize on demand, pause and resume behavioral data collection manually with the pauseBehavioralData() and resumeBehavioralData() methods.

When your DaVinci flow returns a ContinueNode containing a PROTECT collector, detect it using the protectCollectorType constant, initialize the module, and collect the data.

In the following example, daVinciClient is a DaVinci client created with createDaVinciClient(), as described in Configuring the DaVinci module in React Native, and node is the active flow node:

Initializing on demand when a node contains a PROTECT collector
import {
  startProtect,
  collectProtect,
  protectCollectorType,
} from '@ping-identity/rn-protect';

// When a ContinueNode arrives:
if (node.collectors.some((c) => c.type === protectCollectorType)) {
  // Initialize the Protect module
  await startProtect({
    envId: '3072206d-c6ce-ch15-m0nd-f87e972c7cc3',
    isBehavioralDataCollection: true,
  });

  // Collect Protect data for the active PROTECT collector
  await collectProtect(daVinciClient);

  // Submit all collectors and get the next node in the flow
  await daVinciClient.next({});
}

The native layer does apply one setting from the DaVinci flow during collectProtect(): the flow’s behavioral data collection setting.

When the flow disables behavioral data collection, the native SDK pauses it before gathering the risk data.

Pausing and resuming behavioral data capture

Data collection includes collecting behavioral data, such as how the user interacts with the app, to help when performing evaluations.

There are scenarios where you might want to pause the collection of behavioral data:

  • To reduce memory and processor utilization. Continuously collecting behavioral data beyond authentication could reduce the performance of your client app.

  • When you only want to consider device attribute data when performing PingOne Protect evaluations.

You can pause, and also resume behavioral data collection as required.

The PingOne Protect module provides the pauseBehavioralData() and resumeBehavioralData() methods for manually pausing and resuming the capture of behavioral data:

Pausing and resuming behavioral data collection
import { pauseBehavioralData, resumeBehavioralData } from '@ping-identity/rn-protect';

await pauseBehavioralData(); // Pause data collection.
await resumeBehavioralData(); // Resume data collection.

Call pauseBehavioralData() after a successful authentication flow, and resumeBehavioralData() at the start of a new flow.

If you initialize the PingOne Protect module before you create your DaVinci client, and the DaVinci flows you use complete with a success node, the pauseBehavioralDataOnSuccess property pauses behavioral data collection for you. Manual pausing is then only needed if you want to pause at other points.

There are some differences in behavior to be aware of between Android and iOS:

Android
  • Calling either pauseBehavioralData() or resumeBehavioralData() before you have initialized PingOne resolves silently on Android.

  • Android returns PROTECT_INITIALIZE_ERROR on failures.

iOS
  • Calling either pauseBehavioralData() or resumeBehavioralData() before you have initialized PingOne rejects on iOS.

  • iOS returns PROTECT_COLLECT_ERROR on failures.

Both methods also accept an optional logger property, so you can output log messages from the pause and resume operations:

Passing a logger to the pause and resume methods
import { pauseBehavioralData, resumeBehavioralData } from '@ping-identity/rn-protect';
import { logger } from '@ping-identity/rn-logger';

const protectLogger = logger({ level: 'debug' });

await pauseBehavioralData({ logger: protectLogger });
await resumeBehavioralData({ logger: protectLogger });

You can pass the same logger instance to the PingOne Protect module itself through the logger property in the startProtect() configuration.

Returning data to a DaVinci flow for risk evaluation

To perform risk evaluations, the PingOne server requires the captured data.

When your DaVinci flow reaches a PingOne Protect step, the flow returns a ContinueNode containing a PROTECT collector.

Call collectProtect() with your DaVinci client before you advance the flow. The method runs data collection and sets the resulting payload on the native collector, so that the subsequent next() call submits it automatically.

If you use the useDaVinci and useDaVinciForm hooks to manage form state, as described in Stepping through DaVinci flows in React Native, collect the Protect data as soon as the node arrives, then submit the form as normal:

Returning Protect data to a DaVinci flow in a React component
import { useEffect } from 'react';
import { useDaVinci, useDaVinciForm } from '@ping-identity/rn-davinci';
import { collectProtect, protectCollectorType } from '@ping-identity/rn-protect';

const { node, next } = useDaVinci(daVinciClient);

const form = useDaVinciForm(node, {
  handledCollectorTypes: new Set([protectCollectorType]), (1)
});

// Run Protect data collection as soon as a node with a PROTECT collector arrives
useEffect(() => {
  if (node?.type !== 'ContinueNode') return;

  const hasProtectCollector = node.collectors.some(
    (c) => c.type === protectCollectorType,
  );
  if (!hasProtectCollector) return;

  collectProtect(daVinciClient).catch(console.error); (2)
}, [node]);

async function handleSubmit() {
  // The Protect payload has already been collected by the useEffect above
  if (form.canSubmit) {
    await next(form.input); (3)
  }
}

The example above does three things:

  1. Passes protectCollectorType in handledCollectorTypes. 1

    Without this, each PROTECT collector raises an INTEGRATION_REQUIRED issue and forces form.canSubmit to false, because the module treats these collectors as requiring client-side integration.

  2. Runs collectProtect() in a useEffect when the node arrives, rather than waiting for submission. 2

    By the time the user submits, the flow has already collected the Protect payload, and the next(form.input) call forwards it.

  3. Submits the form with next(form.input) guarded by form.canSubmit. 3

    By doing this the submit reflects the readiness of the remaining form fields. The native layer attaches the Protect payload when the flow advances, so form.input needs no Protect value from your app.

If you do not use the hooks, you can call collectProtect() directly before you advance the flow:

Returning Protect data without the React hooks
import { collectProtect } from '@ping-identity/rn-protect';

async function onSubmitHandler() {
  try {
    // Run Protect data collection for the active PROTECT collector
    await collectProtect(daVinciClient);

    // Submit all collectors and get the next node in the flow
    await daVinciClient.next({});
  } catch (err) {
    // Handle the error, see below
  }
}

Handling errors

All promise rejections from the Protect module throw a ProtectError instance.

When you collect data in a useEffect as shown earlier, handle the rejection there, for example with .catch(). Handling the rejection in the effect ensures a collection failure does not go unnoticed if the user still submits the form.

Use instanceof to narrow the error type:

Catching and inspecting ProtectError
import { ProtectError } from '@ping-identity/rn-protect';

try {
  await collectProtect(daVinciClient);
  await daVinciClient.next({});
} catch (err) {
  if (err instanceof ProtectError) {
    console.log(err.code, err.message);
  }
}

The error codes are:

PROTECT_INITIALIZE_ERROR

The native SDK failed to initialize, pause, or resume data collection.

PROTECT_COLLECT_ERROR

The native SDK failed to collect risk signals.

PROTECT_COLLECTOR_NOT_FOUND

The current DaVinci node does not contain an active PROTECT collector.