This module provides native-backed PingOne DaVinci orchestration for React Native on Android and iOS. DaVinci is a flexible authentication and authorization library that drives server-defined flows through a simple node-based API. Your app calls start() to launch a flow, receives a node describing the current step, gathers the required input, and calls next() to advance — repeating until the flow reaches a terminal SuccessNode, ErrorNode, or FailureNode.
Note: This module requires that the
@ping-identity/rn-coremodule is already set up and installed.
# Install & setup the core module
yarn add @ping-identity/rn-core
# Install the rn-davinci module
yarn add @ping-identity/rn-davinci
# If you are developing your app using iOS, run this command
cd ios && pod install
Optional integration packages:
yarn add @ping-identity/rn-storage
yarn add @ping-identity/rn-logger
Use this baseline configuration first.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
const client = createDaVinciClient({
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid', 'profile'],
},
},
});
Add modules.oidc.storage when you need native-backed OIDC token persistence.
Configure it only if you need persistent token storage; otherwise omit storage values.
Set modules.oidc.par to true to enable the Pushed Authorization Request flow. The native
SDK reads the PAR endpoint from the provider's OIDC discovery document.
Warning: If the provider's discovery document does not advertise a PAR endpoint, the native SDK sends the authorization request to an empty URL instead of failing fast. DaVinci's OIDC config does not expose an endpoint override, so confirm PAR support in your provider's discovery document before enabling
par: true.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
import { CacheStrategy, configureOidcStorage } from '@ping-identity/rn-storage';
const oidcStorage = configureOidcStorage({
android: {
fileName: 'davinci-oidc',
keyAlias: 'davinci-oidc',
strongBoxPreferred: true,
cacheStrategy: CacheStrategy.CACHE_ON_FAILURE,
},
ios: {
account: 'com.example.app.oidc',
encryptor: true,
cacheable: false,
},
});
const client = createDaVinciClient({
timeout: 30000,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid', 'profile', 'email'],
par: true,
storage: oidcStorage,
},
},
});
Pass optional integrations through config.modules. The JS API is createDaVinciClient(config).
When provided, the storage handle in modules.oidc.storage must come from
configureOidcStorage(...).
If you install the logger package, pass a JS logger instance created via
@ping-identity/rn-logger.
If the logger package is not installed/configured, do not pass logger values in DaVinci config.
import { createDaVinciClient } from '@ping-identity/rn-davinci';
import { logger } from '@ping-identity/rn-logger';
const jsLogger = logger({ level: 'debug' });
const client = createDaVinciClient({
logger: jsLogger,
modules: {
oidc: {
clientId: 'rn-client',
discoveryEndpoint:
'https://auth.pingone.com/<env-id>/as/.well-known/openid-configuration',
redirectUri: 'com.example.app://callback',
scopes: ['openid'],
},
},
});
const firstNode = await client.start();
const nextNode = await client.next({
collectors: [
{ key: 'user', value: 'demo-user' },
{ key: 'password', value: 'demo-password' },
],
});
const session = await client.user();
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
await client.revoke();
await client.logoutUser();
await client.dispose();
Handle node states explicitly in your UI flow:
const node = await client.start();
switch (node.type) {
case 'ContinueNode':
await client.next({
collectors: [{ key: 'user', value: 'demo-user' }],
});
break;
case 'ErrorNode':
console.log(node.message);
break;
case 'FailureNode':
console.log(node.cause ?? node.message);
break;
case 'SuccessNode':
console.log('Authenticated — session:', node.session.value);
break;
}
When this device is acting as the approving device in an RFC 8628 device
authorization grant, pass the verification_uri_complete URL from the device
authorization response to start(). The DaVinci flow extracts the user_code
from that URL and approves the requesting device.
The useDaVinci hook exposes the option through its start action (see
Use the React hook).
After a DaVinci flow completes successfully, use the following operations to inspect and manage the active user session:
const userSession = await client.user();
if (userSession) {
const refreshedSession = await client.refresh();
const userInfo = await client.userinfo();
await client.revoke();
}
await client.logoutUser();
user() returns token payload (accessToken, optional refreshToken, optional expiresIn).refresh() refreshes token payload for the active user.userinfo() fetches user claims for the active user.revoke() revokes access/refresh tokens for the active user.logoutUser() signs out and clears the active DaVinci user session.useDaVinci does not auto-advance nodes. Progression policy is app-controlled via explicit next(...) calls.
import { useDaVinci } from '@ping-identity/rn-davinci';
const { node, start, next, loading, error } = useDaVinci(client);
await start();
if (node?.type === 'ContinueNode') {
await next({
collectors: [{ key: 'user', value: 'demo-user' }],
});
}
To approve an RFC 8628 device authorization grant, pass the verificationUri
option to start (see Approving a device authorization grant):
await start({
verificationUri: 'https://example.com/device?user_code=WDJB-MJHT',
});
After the DaVinci flow authenticates the user, the native SDK extracts the
user_code from that URL and approves the requesting device automatically;
no extra submit step is required in the app.
import { DaVinciProvider, useDaVinci } from '@ping-identity/rn-davinci';
function App(): React.ReactElement {
return (
<DaVinciProvider config={config}>
<AuthNavigator />
</DaVinciProvider>
);
}
function LoginScreen(): React.ReactElement {
const { node, start, next, loading } = useDaVinci();
if (!node) {
return <Button title="Sign In" onPress={start} />;
}
if (node.type === 'SuccessNode') {
return <Text>Authenticated</Text>;
}
if (node.type === 'ContinueNode') {
return <DaVinciForm node={node} onNext={next} loading={loading} />;
}
return <Text>{node.message}</Text>;
}
useDaVinciFormimport { useDaVinci, useDaVinciForm } from '@ping-identity/rn-davinci';
const { node, next } = useDaVinci(client);
const form = useDaVinciForm(node);
form.setValueByType('TEXT', 'demo-user');
form.setValueByType('PASSWORD', 'demo-password');
if (form.canSubmit) {
await next(form.input);
}
useDaVinciForm is headless. It manages normalized collectors and submit planning, but does not render UI and does not auto-run collectors.
Use validate(collectorKey, value) to validate one active collector, such as when a field loses focus. It returns that collector's validation errors without calling next():
const { validate } = useDaVinci(client);
const errors = await validate('email', 'not-an-email');
if (errors.length > 0) {
// Example: [{ code: 'REGEX_ERROR', message: 'Invalid email' }]
}
validate() applies value to the active native collector before checking it. The value remains on the collector and is included in a later next() call. An empty array means the value has no validation errors or that the collector has no native validator.
Each normalized collector includes executionMode and requiresUserInput.
executionMode |
Meaning | requiresUserInput default |
|---|---|---|
manual |
Collector value is submitted from form/planned input. | true |
immediate |
Activating the collector immediately advances the flow without waiting for other fields. | false |
output_only |
Display/label collector, no input value expected. | false |
integration_required |
Collector is handled by an external integration package before submit. | false |
unsupported |
Collector type is not currently handled by the bridge or any registered integration. | false |
FLOW_BUTTON, FLOW_LINK, and ACTION collectors bypass other form fields and immediately advance the flow. Use submitFlow(key) inside a DaVinciProvider tree:
const form = useDaVinciForm(node);
// Activates "Forgot Password" flow link directly — no other field values are included.
await form.submitFlow('forgot-password');
The following collector types are supported on Android and iOS:
| Collector Type | Description | Input Handling |
|---|---|---|
TEXT |
Single-line text input. | Manual input |
PASSWORD |
Masked password input. | Manual input |
PASSWORD_VERIFY |
Password-confirmation variant of PASSWORD. |
Manual input |
SINGLE_SELECT |
Single-select input. | Manual input |
DROPDOWN |
Single-select dropdown. | Manual input |
RADIO |
Single-select radio group. | Manual input |
MULTI_SELECT |
Multi-select input. | Manual input |
COMBOBOX |
Multi-select combobox. | Manual input |
CHECKBOX |
Multi-select checkbox group. | Manual input |
PHONE_NUMBER |
Phone number input with country code. | Manual input |
DEVICE_REGISTRATION |
Device picker for registration. | Manual input |
DEVICE_AUTHENTICATION |
Device picker for authentication. | Manual input |
SUBMIT_BUTTON |
Triggers form submission immediately. | Immediate |
ACTION |
Action button that advances the flow immediately. | Immediate |
FLOW_BUTTON |
Flow button that advances the flow immediately. | Immediate |
FLOW_LINK |
Flow link that advances the flow immediately. | Immediate |
SINGLE_CHECKBOX |
Single checkbox or toggle (boolean field). | Manual input |
LABEL |
Read-only display content. | Output-only |
READ_ONLY_TEXT |
Read-only text / agreement content. | Output-only |
POLLING |
Async polling collector — see Polling and QR code flows. | Output-only |
QR_CODE |
Display-only QR code — see Polling and QR code flows. | Output-only |
FIDO2 |
FIDO passkey registration or authentication; narrow by action. |
Integration |
Integration-dependent collectors are surfaced in node payloads with
executionMode: 'integration_required'. Their minimum generic shape is key,
type, and optional raw; the owning integration package may provide additional
fields and the operation needed before next(). Use the integration package's
exported collector type/constant and pass its type in handledCollectorTypes:
import { socialLoginCollectorType } from '@ping-identity/rn-external-idp';
const form = useDaVinciForm(node, {
handledCollectorTypes: new Set([socialLoginCollectorType]),
});
Known collector-specific fields should be accessed only after narrowing with the
owning package's type or helper. Generic integration collectors do not guarantee
fields such as label, options, or value.
FIDO2 is owned by @ping-identity/rn-fido. It is one collector type, with
action: 'REGISTER' for publicKeyCredentialCreationOptions or
action: 'AUTHENTICATE' for publicKeyCredentialRequestOptions. Create a FIDO
client before the node is normalized so it registers FIDO2 with the integration
registry, and include the type in handledCollectorTypes:
import {
createFidoClient,
fidoCollectorType,
type FidoCollector,
} from '@ping-identity/rn-fido';
const fido = createFidoClient();
const form = useDaVinciForm(node, {
handledCollectorTypes: new Set([fidoCollectorType]),
});
for (const collector of node.collectors) {
const fidoCollector = collector as FidoCollector;
if (fidoCollector.action === 'REGISTER') {
await fido.registerForDaVinci(daVinci, { index: 0 });
} else if (fidoCollector.action === 'AUTHENTICATE') {
await fido.authenticateForDaVinci(daVinci, { index: 0 });
}
}
// The native collector retains the ceremony result for submission.
await daVinci.next({ collectors: [] });
The ceremony must be completed before calling next, and the FIDO collector key
must not be passed in next input. These DaVinci methods use native ceremony
defaults; no React Native ceremony customization options are exposed.
When the native SDK cannot instantiate a collector from the server payload, the bridge surfaces it in ContinueNode.unsupportedFields:
if (node.type === 'ContinueNode' && node.unsupportedFields?.length) {
console.warn('Unsupported fields present:', node.unsupportedFields);
}
Each entry has key and type so the UI can render a placeholder or block submission.
All promise rejections throw a DaVinciError instance, which extends PingError extends Error.
Use instanceof to narrow the error type:
import { DaVinciError } from '@ping-identity/rn-davinci';
try {
await client.start();
} catch (err) {
if (err instanceof DaVinciError) {
console.log(err.code, err.type, err.message);
}
}
Stable DaVinci error codes:
DAVINCI_CONFIG_ERRORDAVINCI_START_ERRORDAVINCI_NEXT_ERRORDAVINCI_VALIDATE_ERRORDAVINCI_COLLECTOR_APPLY_ERRORDAVINCI_SESSION_ERRORDAVINCI_LOGOUT_ERRORDAVINCI_DISPOSE_ERRORDAVINCI_POLL_ERRORDAVINCI_ARGUMENT_ERRORDAVINCI_STATE_ERRORDAVINCI_MISSING_INTEGRATION_ERRORDAVINCI_UNKNOWN_ERRORThis project is licensed under the MIT License - see the LICENSE file for details