Ping SDKs

Configure client apps for one-time passcodes

Select your platform to discover how to configure your client application to use one-time passcodes during an authentication flow.

Configure an Android app to use one-time passcodes

Your app must handle the DeviceRegistrationCollector and DeviceAuthenticationCollector collector types that DaVinci sends. These contain details of the available one-time passcode delivery methods.

Learn more about setting up an app to receive collectors in the Tutorials.

Loop through the collectors returned by DaVinci, ensuring you handle the one-time passcode collectors:

continueNode.collectors.forEach {
    when (it) {

        // Other collectors here...

        is DeviceRegistrationCollector -> {
            // Compose to display DeviceRegistrationCollector
            DeviceRegistration(it, onNext)
        }
        is DeviceAuthenticationCollector -> {
            // Compose to display DeviceAuthenticationCollector
            DeviceAuthentication(it, onNext)
        }
        // Compose to display PhoneNumberCollector
        is PhoneNumberCollector -> PhoneNumber (it, onNodeUpdated)
    }
}

After collecting the data for a node you can proceed to the next node in the flow by calling the next() method on your current node object.

Learn more about node.next() in Continuing a DaVinci flow in the Android deep-dive tutorial.

Sample collector view files:

Configure an iOS app to use one-time passcodes

Your app must handle the DeviceRegistrationCollector and DeviceAuthenticationCollector collector types that DaVinci sends. These contain details of the available one-time passcode delivery methods.

Learn more about setting up an app to receive collectors in the Tutorials.

Loop through the collectors returned by DaVinci, ensuring you handle the one-time passcode collectors:

ForEach(continueNode.collectors , id: \.id) { collector in
    switch collector {

    // Other collectors here...

    case is DeviceRegistrationCollector:
        if let deviceRegistrationCollector = collector as? DeviceRegistrationCollector {
            DeviceRegistrationView(field: deviceRegistrationCollector, onNext: onNext)
        }
    case is DeviceAuthenticationCollector:
        if let deviceAuthenticationCollector = collector as? DeviceAuthenticationCollector {
            DeviceAuthenticationView(field: deviceAuthenticationCollector, onNext: onNext)
        }
    case is PhoneNumberCollector:
        if let phoneNumberCollector = collector as? PhoneNumberCollector {
            PhoneNumberView(field: phoneNumberCollector, onNodeUpdated: onNodeUpdated)
        }
    default:
        EmptyView()
    }
}

After collecting the data for a node you can proceed to the next node in the flow by calling the next() method on your current node object.

Learn more about node.next() in Continuing a DaVinci flow in the iOS deep-dive tutorial.

Sample collector view files:

Configure a JavaScript app to use one-time passcodes

Your app must handle the DeviceRegistrationCollector and DeviceAuthenticationCollector collector types that DaVinci sends. These contain details of the available one-time passcode delivery methods.

You might also need to handle PhoneNumberCollector collectors, if the user chooses Voice or SMS as their MFA method, for example.

Learn more about setting up an app to receive collectors in the Tutorials.

Loop through the collectors returned by DaVinci, ensuring you handle the one-time passcode collectors:

const collectors = davinciClient.getCollectors();

collectors.forEach((collector) => {
  if (
    collector.type === 'DeviceRegistrationCollector' ||
    collector.type === 'DeviceAuthenticationCollector'
  ) {
    deviceComponent(
      collector, // Object of the collector
      davinciClient.update(collector), // Return updater for collector
    );
  } else if (collector.type === 'PhoneNumberCollector') {
    phoneNumberComponent(
      collector, // Object of the collector
      davinciClient.update(collector), // Return updater for collector
    );
  }
});

In this example, a deviceComponent or phoneNumberComponent component handles rendering the relevant user interface. Pass the selected option into the updater() method:

Example deviceComponent file to render OTP selection
export default function deviceComponent(
  collector: DeviceRegistrationCollector | DeviceAuthenticationCollector,
  updater: Updater,
) {
  const groupLabel = collector.output.label || 'Select an option';

  // Bind to options to handle user selection
  function eventHandler(event) {
    const selectedValue = // get value from event's target element

    updater(selectedValue);
  }

  // Iterate over the options and render each
  for (const option of collector.output.options) {
    const elementLabel = option.label;
    const elementValue = option.value;

    // Render each element to DOM
  }
}
Example phoneNumberComponent file to render OTP selection
export default function phoneNumberComponent(
  collector: PhoneNumberCollector,
  updater: Updater,
) {
  const phoneLabel = collector.output.label || 'Phone Number';

  // Get default or existing values
  const countryCodeValue = collector.output.value.countryCode;
  const phoneNumberValue = collector.output.value.phoneNumber;

  // This just uses a mutable object for simplicity
  let phoneObject = {
    countryCode: countryCodeValue,
    phoneNumber: phoneNumberValue,
  };

  // Add change event listener to country code select
  function handleCountryCodeEvent(event) {
    const selectedValue = // get value from event's target element

    // Mutate object then pass to updater
    phoneNumber = { ...phoneObject, countryCode: selectedValue };
    updater(phoneNumber);
  }

  // Add change event listener to phone number input
  function handlePhoneNumberEvent(event) {
    const selectedValue = // get value from event's target element

    // Mutate object then pass to updater
    phoneNumber = { ...phoneObject, phoneNumber: selectedValue };
    updater(phoneNumber);
  }

  // Render both country code select and phone number input
}