Ping SDKs with DaVinci Flows
Introducing the DaVinci client for DaVinci flows
The DaVinci clients provide powerful orchestration capabilities with PingOne DaVinci. They enable you to consume DaVinci flows to meet your use cases, all while providing a native Android or iOS, or a single-page app JavaScript experience.
You have complete control of your UI, so you can create the tailored experience you desire for your end users, all while leaving the DaVinci client to do the heavy lifting of communication between your app and your DaVinci flows.
Configure the DaVinci client
The DaVinci clients are designed to be flexible and can be customized to suit many different situations.
Learn more about configuring and customizing the DaVinci clients in the following sections:
Configure DaVinci client properties
You need to configure certain settings so that the DaVinci client can connect to your PingOne instance to step through your DaVinci flows and authenticate your users.
The method you use to configure these settings depends on which platform you are using.
Configure DaVinci client for Android properties
Configure DaVinci client for Android properties to connect to PingOne and step through an associated DaVinci flow.
Installing and importing the DaVinci client
To use the DaVinci client for Android, add the relevant dependencies to your project:
-
In the Project tree view of your Android Studio project, open the
Gradle Scripts/build.gradle.kts
file for the DaVinci module. -
In the
dependencies
section, add the following:implementation("com.pingidentity.sdks:davinci:1.0.0")
gradleExample of thedependencies
section after editing:dependencies { val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) // DaVinci client implementation("com.pingidentity.sdks:davinci:1.0.0") ... implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) }
gradle
Configuring connection properties
Create an instance of the DaVinci
object and use the underlying Oidc
module to set configuration properties.
The following properties are available for configuring the DaVinci client for Android:
Property | Description | Required? | ||
---|---|---|---|---|
|
Your PingOne server’s Example:
|
Yes |
||
|
The For example, |
Yes |
||
|
A set of scopes to request when performing an OAuth 2.0 authorization flow. For example, |
Yes |
||
|
The
For example, |
Yes |
||
|
A timeout, in seconds, for each request that communicates with the server. Default is |
No |
||
|
Request which flow the PingOne server uses by adding an Authentication Context Class Reference (ACR) parameter. Enter a single DaVinci policy by using its flow policy ID. Example:
|
No |
Example
The following shows an example DaVinci client configuration, using the underlying Oidc
module:
import com.pingidentity.davinci.DaVinci
import com.pingidentity.davinci.module.Oidc
val daVinci = DaVinci {
module(Oidc) {
clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
discoveryEndpoint = "https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/" +
"as/.well-known/openid-configuration"
scopes = mutableSetOf("openid", "profile", "email", "address", "revoke")
redirectUri = "org.forgerock.demo://oauth2redirect"
}
}
Stepping through DaVinci flows
To authenticate your users the DaVinci client for Android must start the flow, and step through each node.
Starting a DaVinci flow
To start a DaVinci flow, call the start()
method:
val node = daVinci.start()
Determining DaVinci flow node type
Each step of the flow returns one of four node types:
ContinueNode
-
This type indicates there is input required from the client. The
node
object for this type contains acollector
object, which describes the information it requires from the client.Learn more in Handling DaVinci flow collectors in continue nodes.
SuccessNode
-
This type indicates the flow is complete, and authentication was successful.
Learn more in Handling DaVinci flow success nodes.
ErrorNode
-
This type indicates an error in the data sent to the server. For example, an email address in an incorrect format, or a password that does not meet complexity requirements.
You can correct the error and resubmit to continue the flow.
Learn more in Handling DaVinci flow error nodes.
FailureNode
-
This type indicates that the flow could not be completed and must be restarted. This can be caused by a server error, or a timeout.
Learn more in Handling DaVinci flow failure nodes.
You can use the helper functions to determine which node type the server has returned:
node
type.when (node) {
is ContinueNode -> {}
is ErrorNode -> {}
is FailureNode -> {}
is SuccessNode -> {}
}
Handling DaVinci flow collectors in continue nodes
The ContinueNode
type contains collectors
. These collectors define what information or action to request from the user, or client device.
There are specific collector types. For example there are TextCollector
and PasswordCollector
types.
To complete a DaVinci flow we recommend that you implement a component for each connector type you will encounter in the flow. Then you can iterate through the flow and handle each collector as you encounter it.
ContinueNode
node.collectors.forEach {
when(it) {
is TextCollector → it.value = "My First Name"
is PasswordCollector → it.value = "My Password"
is SubmitCollector → it.value = "click me"
is FlowCollector → it.value = "Forgot Password"
}
}
Continuing a DaVinci flow
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.
next()
val next = node.next()
You do not need to pass any parameters into the |
The server responds with a new node
object, just like when starting a flow initially.
Loop again through conditional checks on the new node’s type to render the appropriate UI or take the appropriate action.
Handling DaVinci flow error nodes
DaVinci flows return the ErrorNode
type when it receives data that is incorrect, but you can fix the data and resubmit. For example, an email value submitted in an invalid format or a new password that is too short.
You can retrieve the error message by using node.message()
, and the raw JSON response with node.input
.
val node = daVinci.start() // Start the flow
//Determine the Node Type
when (node) {
is ContinueNode -> {}
is ErrorNode -> {
node.message() // Retrieve the cause of the error
}
is FailureNode -> {}
is SuccessNode -> {}
}
This is different than a |
You can retain a reference to the node
you submit in case the next node
you receive is an ErrorNode
type. If so, you can re-render the previous form, and inject the error information from the new ErrorNode
node.
After the user revises the data call next()
as you did before.
Handling DaVinci flow failure nodes
DaVinci flows return the FailureNode
type if there has been an issue that prevents the flow from continuing. For example, the flow times out or suffers a server error.
You can retrieve the cause of the failure by using node.cause()
, which is a Throwable
object.
FailureNode
typeval node = daVinci.start() // Start the flow
//Determine the Node Type
when (node) {
is ContinueNode -> {}
is ErrorNode -> {}
is FailureNode -> {
node.cause() // Retrieve the error message
}
is SuccessNode -> {}
}
You should offer to restart the flow on receipt of a FailureNode
type.
Handling DaVinci flow success nodes
DaVinci flows return the SuccessNode
type when the user completes the flow and PingOne issues them a session.
To retrieve the existing session, you can use the following code:
SuccessNode
typeval user: User? = daVinci.user()
user?.let {
it.accessToken()
it.revoke()
it.userinfo()
it.logout()
}
Leverage Jetpack Compose
The following shows how you could use the DaVinci client with Jetpack Compose:
// Define State that listen by the View
var state = MutableStateFlow<Node>(Empty)
//Start the DaVinci flow
val next = daVinci.start()
// Update the state
state.update {
next
}
fun next(node: ContinueNode) {
viewModelScope.launch {
val next = node.next()
state.update {
next
}
}
}
when (val node = state.node) {
is ContinueNode -> {}
is ErrorNode -> {}
is FailureNode -> {}
is SuccessNode -> {}
}
Configure DaVinci client for iOS properties
Configure DaVinci client module for iOS properties to connect to PingOne and step through an associated DaVinci flow.
Installing and importing the DaVinci client
To use the DaVinci client for iOS, use Swift Package Manager (SPM) to add the dependencies to your project:
You can install this by using SPM (Swift Package Manager) on the generated iOS project.
-
In Xcode, select your project and navigate to Package Dependencies.
-
Click the sign, and add the Ping SDK for iOS repository,
https://github.com/ForgeRock/forgerock-ios-sdk.git
. -
Add the
davinci
library to the project.
Configuring connection properties
Create an instance of the DaVinci
class by passing configuration to the createDaVinci
method. This uses the underlying Oidc
module to set configuration properties.
The following properties are available for configuring the DaVinci client for iOS:
Property | Description | Required? | ||
---|---|---|---|---|
|
Your PingOne server’s Example:
|
Yes |
||
|
The For example, |
Yes |
||
|
A set of scopes to request when performing an OAuth 2.0 authorization flow. For example, |
Yes |
||
|
The
For example, |
Yes |
||
|
A timeout, in seconds, for each request that communicates with the server. Default is |
No |
||
|
Request which flow the PingOne server uses by adding an Authentication Context Class Reference (ACR) parameter. Enter a single DaVinci policy by using its flow policy ID. Example:
|
No |
Example
The following shows an example DaVinci client configuration, using the underlying Oidc
module:
let daVinci = DaVinci.createDaVinci { config in
// Oidc as module
config.module(OidcModule.config) { oidcValue in
oidcValue.clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
oidcValue.discoveryEndpoint = "https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
oidcValue.scopes = ["openid", "profile", "email", "address", "revoke"]
oidcValue.redirectUri = "org.forgerock.demo://oauth2redirect"
}
}
Stepping through DaVinci flows
To authenticate your users the DaVinci client for iOS must start the flow, and step through each node.
Starting a DaVinci flow
To start a DaVinci flow, call the start()
method:
var node = await daVinci.start()
Determining DaVinci flow node type
Each step of the flow returns one of four node types:
ContinueNode
-
This type indicates there is input required from the client. The
node
object for this type contains acollector
object, which describes the information it requires from the client.Learn more in Handling DaVinci flow collectors in continue nodes.
SuccessNode
-
This type indicates the flow is complete, and authentication was successful.
Learn more in Handling DaVinci flow success nodes.
ErrorNode
-
This type indicates an error in the data sent to the server. For example, an email address in an incorrect format, or a password that does not meet complexity requirements.
You can correct the error and resubmit to continue the flow.
Learn more in Handling DaVinci flow error nodes.
FailureNode
-
This type indicates that the flow could not be completed and must be restarted. This can be caused by a server error, or a timeout.
Learn more in Handling DaVinci flow failure nodes.
You can use the helper functions to determine which node type the server has returned:
node
type.switch (node) {
case is ContinueNode: do {}
case is ErrorNode: do {}
case is FailureNode: do {}
case is SuccessNode: do {}
}
Handling DaVinci flow collectors in continue nodes
The ContinueNode
type contains collectors
. These collectors define what information or action to request from the user, or client device.
There are specific collector types. For example there are TextCollector
and PasswordCollector
types.
To complete a DaVinci flow we recommend that you implement a component for each connector type you will encounter in the flow. Then you can iterate through the flow and handle each collector as you encounter it.
ContinueNode
node.collectors.forEach { item in
switch(item) {
case is TextCollector:
(item as! TextCollector).value = "My First Name"
case is PasswordCollector:
(item as! PasswordCollector).value = "My Password"
case is SubmitCollector:
(item as! SubmitCollector).value = "click me"
case is FlowCollector:
(item as! FlowCollector).value = "Forgot Password"
}
}
Continuing a DaVinci flow
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.
next()
let next = node.next()
You do not need to pass any parameters into the |
The server responds with a new node
object, just like when starting a flow initially.
Loop again through conditional checks on the new node’s type to render the appropriate UI or take the appropriate action.
Handling DaVinci flow error nodes
DaVinci flows return the ErrorNode
type when it receives data that is incorrect, but you can fix the data and resubmit. For example, an email value submitted in an invalid format or a new password that is too short.
You can retrieve the error message by using node.message()
, and the raw JSON response with node.input
.
let node = await daVinci.start() //Start the flow
//Determine the Node Type
switch (node) {
case is ContinueNode: do {}
case is FailureNode: do {}
case is ErrorNode:
(node as! ErrorNode).message //Retrieve the error message
case is SuccessNode: do {}
}
This is different than a |
You can retain a reference to the node
you submit in case the next node
you receive is an ErrorNode
type. If so, you can re-render the previous form, and inject the error information from the new ErrorNode
node.
After the user revises the data call next()
as you did before.
Handling DaVinci flow failure nodes
DaVinci flows return the FailureNode
type if there has been an issue that prevents the flow from continuing. For example, the flow times out or suffers a server error.
You can retrieve the cause of the failure by using node.cause()
, which is an Error
instance.
FailureNode
typelet node = await daVinci.start() //Start the flow
//Determine the Node Type
switch (node) {
case is ContinueNode: do {}
case is FailureNode:
(node as! FailureNode).cause //Retrieve the cause of the Failure
case is ErrorNode: do {}
case is SuccessNode: do {}
}
You should offer to restart the flow on receipt of a FailureNode
type.
Handling DaVinci flow success nodes
DaVinci flows return the SuccessNode
type when the user completes the flow and PingOne issues them a session.
To retrieve the existing session, you can use the following code:
SuccessNode
typelet user: User? = await daVinci.user()
_ = await user?.token()
await user?.revoke()
_ = await user?.userinfo(cache: false)
await user?.logout()
Leverage SwiftUI
The following shows how you could use the DaVinci client with SwiftUI:
//Define State that listen by the View
@Published var state: Node = EmptyNode()
//Start the DaVinci flow
let next = await daVinci.start()
//Update the state
state = next
func next(node: ContinueNode) {
val next = await node.next()
state = next
}
if let node = state.node {
switch node {
case is ContinueNode:
// Handle ContinueNode case
break
case is ErrorNode:
// Handle Error case
break
case is FailureNode:
// Handle Failure case
break
case is SuccessNode:
// Handle Success case
break
default:
break
}
}
Configure DaVinci client for JavaScript properties
Configure DaVinci client properties to connect to PingOne and step through an associated DaVinci flow.
Installing and importing the DaVinci client
To install and import the DaVinci client:
-
Install the DaVinci client into your JavaScript apps using
npm
:Install the DaVinci clientnpm install @forgerock/davinci-client
shell -
Import the DaVinci client as a named import:
Import the DaVinci clientimport { davinci } from '@forgerock/davinci-client';
javascript
Configuring connection properties
Pass a config
object into davinci
to initialize a client with the required connection properties.
The following properties are available for configuring the DaVinci client for JavaScript:
Property | Description | Required? |
---|---|---|
|
An interface for configuring how the SDK contacts the PingAM instance. Contains |
Yes |
|
Your PingOne server’s Example:
|
Yes |
|
A timeout, in milliseconds, for each request that communicates with your server. For example, for 30 seconds specify Defaults to |
No |
|
The For example, |
Yes |
|
A list of scopes to request when performing an OAuth 2.0 authorization flow, separated by spaces. For example, |
No |
|
The type of OAuth 2.0 flow to use, either Defaults to |
No |
Example
The following shows a full DaVinci client configuration:
import { davinci } from '@forgerock/davinci';
const davinciClient = await davinci({
config: {
clientId: '6c7eb89a-66e9-ab12-cd34-eeaf795650b2',
serverConfig: {
wellknown: 'https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration',
timeout: 3000,
},
scope: 'openid profile email address revoke',
responseType: 'code',
},
});
Stepping through DaVinci flows
To authenticate your users the Ping SDK for JavaScript DaVinci client must start the flow, and step through each node.
Starting a DaVinci flow
To start a DaVinci flow, call the start()
method on your new client object:
let node = await davinciClient.start();
Determining DaVinci flow node type
Each step of the flow returns one of four node types:
continue
-
This type indicates there is input required from the client. The
node
object for this type contains a list ofcollector
objects, which describe the information it requires from the client.Learn more in Handling DaVinci flow collectors in continue nodes.
success
-
This type indicates the flow is complete, and authentication was successful.
Learn more in Handling DaVinci flow success nodes.
error
-
This type indicates an error in the data sent to the server. For example, an email address in an incorrect format, or a password that does not meet complexity requirements.
You can correct the error and resubmit to continue the flow.
Learn more in Handling DaVinci flow error nodes.
failure
-
This type indicates that the flow could not be completed and must be restarted. This can be caused by a server error, or a timeout.
Learn more in Handling DaVinci flow failure nodes.
Use node.status
to determine which node type the server has returned:
node.status
propertylet node = await davinciClient.start();
switch (node.status) {
case 'continue':
return renderContinue();
case 'success':
return renderSuccess();
case 'error':
return renderError();
default: // Handle 'failure' node type
return renderFailure();
}
Handling DaVinci flow collectors in continue nodes
The continue
node type contains a list of collector
objects. These collectors define what information or action to request from the user, or browser.
There are two categories of collector:
SingleValueCollector
-
A request for a single value, such as a username or password string.
Show the interface for
SingleValueCollector
export interface SingleValueCollector { category: 'SingleValueCollector'; type: SingleValueCollectorTypes; id: string; name: string; input: { key: string; value: string | number | boolean; type: string; }; output: { key: string; label: string; type: string; value: string; }; }
javascript ActionCollector
-
Represents an action the user can take, such as a button or a link they can select. For example, a link that jumps to a password recovery section of a flow.
Show the interface for
ActionCollector
export interface ActionCollector { category: 'ActionCollector'; type: ActionCollectorTypes; id: string; name: string; output: { key: string; label: string; type: string; url?: string; }; }
javascript
Within each category of collector there are specific collector types. For example, in the SingleValueCollector
category there are TextCollector
and PasswordCollector
types.
To complete a DaVinci flow we recommend that you implement a component for each connector type you will encounter in the flow. Then you can iterate through the flow and handle each collector as you encounter it.
Example 1. TextCollector
This example shows how to update a collector with a value gathered from your user.
Pass both a collector
and updater
object into a component that renders the appropriate user interface, captures the user’s input, and then updates the collector, ready to return to the server.
TextCollector
mappingconst collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'TextCollector') {
renderTextCollector(collector, davinciClient.update(collector));
}
});
Mutating the The internal data the client stores is immutable and can only be updated using the provided APIs, not through property assignment. |
Your renderTextCollector
would resemble the following:
TextCollector
updater componentfunction renderTextCollector(collector, updater) {
// ... component logic
function onClick(event) {
updater(event.target.value);
}
// render code
}
Example 2. FlowCollector
This example shows how change from the current flow to an alternate flow, such as a reset password or registration flow.
To switch flows, call the flow
method on the davinciClient
passing the key
property to identify the new flow.
FlowCollector
mappingconst collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'FlowCollector') {
renderFlowCollector(collector, davinciClient.flow(collector));
}
});
This returns a function you can call when the user interacts with it.
flowCollector
componentfunction renderFlowCollector(collector, startFlow) {
// ... component logic
function onClick(event) {
startFlow();
}
// render code
}
Example 3. SubmitCollector
This example shows how submit the current node and its collected values back to the server. The collection of the data is already complete so an updater component is not required. This collector only renders the button for the user to submit the collected data.
SubmitCollector
mappingconst collectors = davinciClient.getCollectors();
collectors.map((collector) => {
if (collector.type === 'SubmitCollector') {
renderSubmitCollector(
collector, // This is the only argument you will need to pass
);
}
});
Continuing a DaVinci flow
After collecting the data for a node you can proceed to the next node in the flow by calling the next()
method on your DaVinci client object.
This can be the result of a user clicking on the button rendered from the SubmitCollector
, from the submit
event of an HTML form, or by programmatically triggering the submission in the application layer.
next()
let nextStep = davinciClient.next();
You do not need to pass any parameters into the |
The server responds with a new node
object, just like when starting a flow initially.
Loop again through conditional checks on the new node’s type to render the appropriate UI or take the appropriate action.
Handling DaVinci flow error nodes
DaVinci flows return the error
node type when it receives data that is incorrect, but you can fix the data and resubmit. For example, an email value submitted in an invalid format or a new password that is too short.
This is different than a failure
node type which you cannot resubmit and instead you must restart the entire flow.
You can retain a reference to the node
you submit in case the next node
you receive is an error
type. If so, you can re-render the previous form, and inject the error information from the new error
node.
After the user revises the data call next()
as you did before.
Handling DaVinci flow failure nodes
DaVinci flows return the failure
node type if there has been an issue that prevents the flow from continuing. For example, the flow times out or suffers an HTTP 500 server error.
You should offer to restart the flow on receipt of a failure
node type.
failure
node typeconst node = await davinciClient.next();
if (node.status === 'failure') {
const error = davinciClient.getError();
renderError(error);
// ... user clicks button to restart flow
const freshNode = davinciClient.start();
}
Handling DaVinci flow success nodes
DaVinci flows return the success
node type when the user completes the flow and PingOne issues them a session.
On receipt of a success
node type you should use the OAuth 2.0 authorization Code and state properties from the node and use them to obtain an access token on behalf of the user.
To obtain an access token, leverage the Ping SDK for JavaScript.
// ... other imports
import { davinci } from '@forgerock/davinci-client';
import { Config, TokenManager } from '@forgerock/javascript-sdk';
// ... other config or initialization code
// This Config.set accepts the same config schema as the davinci function
Config.set(config);
const node = await davinciClient.next();
if (node.status === 'success') {
const clientInfo = davinciClient.getClient();
const code = clientInfo.authorization?.code || '';
const state = clientInfo.authorization?.state || '';
const tokens = await TokenManager.getTokens({
query: {
code, state
}
});
// user now has session and OIDC tokens
}
Default DaVinci client headers
The DaVinci clients send a number of header values to the server with every outgoing request.
These headers can help you identify the client in your flows and help you correlate actions to a transaction in DaVinci audit logs. You can also use these values to alter the course of a DaVinci flow.
The default headers the DaVinci client always include are as follows:
x-requested-with
-
Identifies that the request comes from an app built with the Ping DaVinci client.
Default value:
ping-sdk
x-requested-platform
-
Identifies the platform the DaVinci client is running on.
Default values:
Platform Value Android
android
iOS
ios
JavaScript
javascript
interactionId
-
Returns the
interactionId
value provided by the server to help trace the transaction in server audit logs and dashboards.Example value:
18484499-c551-4d99-c415-b01c79bedb47
interactionToken
-
Returns the
interactionToken
value provided by the server to help trace the transaction in server audit logs and dashboards.Example value:
437783552aa3a5a8f0041028d5b8dac2d72f7e7ebd7f88a966fb690402f6571b964c3df8897cbe542e62721070b3f6fcc946f4dd2bc80b9df332d39657fcaaad4651884093a786910d6f1337bd8dda17b4fca48e8fa481469ce0df1f676e46d1a6fc30577d910010d4a2530f2d02e69f436d610992c79fcb0ca87131d0df3f9a
Ping SDK for PingOne DaVinci tutorials
These tutorials walk you through updating a provided sample app so that it connects to a PingOne tenant to authenticate a user using the PingOne sign-on with sessions DaVinci flow.
This flow allows users to register, authenticate, and verify their email address with PingOne. It combines the PingOne Sign On and Password Reset flow, which allows users to reset or recover their passwords, with the PingOne Registration and Email Verification flow, which allows users to register and verify their email. In addition, this flow checks for an active user session before prompting for authentication.

You will implement code that:
-
Configures the app with the connection settings for your PingOne instance.
-
Starts a DaVinci flow
-
Renders UI depending on the type of node encountered in the flow
-
Returns responses to each node encountered
-
Gets an access token for the user on completion of the flow
-
Gets the user’s details from PingOne, such as their full name and email address
-
Logs the user out of PingOne
To start, choose the platform to host your app:
DaVinci flow tutorial for Android
This tutorial walks you through updating a provided sample app so that it connects to a PingOne tenant to authenticate a user using the PingOne sign-on with sessions DaVinci flow.
This flow allows users to register, authenticate, and verify their email address with PingOne.
Before you begin
To successfully complete this tutorial refer to the prerequisites in this section.
The tutorial also requires a configured PingOne instance.
Compatibility
- PingOne
-
-
Your PingOne instance must have DaVinci enabled.
-
This tutorial only supports the PingOne sign-on with sessions DaVinci flow.
-
- DaVinci flows
-
Ensure your user interface nodes only employ these supported connectors and capabilities:
-
HTTP Connector
-
Custom HTML Template
-
Compatible fields within the Custom HTML Template capability are:
-
Text field: Lets the user enter text
-
Password field: Lets the user enter a password which cannot be read from the screen
-
Submit Button: Lets the user submit field data and proceed
-
Flow Button: Lets the user trigger a new process without submitting field data
Verify that your flow does not depend on any unsupported elements:
-
SKPolling components: SKPolling components cannot be processed by the SDK and should not be included.
-
Variable field names: You cannot use variables or other values from a node’s input schema in the names of any field used by the SDK.
-
Images: Images included in the flow cannot be passed to the SDK.
-
Additional text: Titles or other text cannot be passed to the SDK.
-
- Android
-
This sample requires at least Android API 23 (Android 6.0)
- Java
-
This sample requires at least Java 8 (v1.8).
Prerequisites
- Android Studio
-
Download and install Android Studio, which is available for many popular operating systems.
- An Android emulator or physical device
-
To try the quick start application as you develop it, you need an Android device. To add a virtual, emulated Android device to Android Studio, refer to Create and manage virtual devices, on the Android Developers website.
Server configuration
You must configure your PingOne instance for use with the DaVinci client.
Ask your PingOne administrator to complete the following tasks:
-
Configure a DaVinci flow
-
Create a DaVinci application
-
Configure PingOne for DaVinci flow invocation
To learn how to complete these steps, refer to Launching a flow with a Ping SDK in the PingOne DaVinci documentation.
Step 1. Download the samples
To start this tutorial, you need to download the ForgeRock SDK sample apps repo, which contains the projects you will use.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
Use the URL to clone the repository to a suitable location.
For example, from the command-line you could run:
git clone https://github.com/ForgeRock/sdk-sample-apps.git.git
shell
-
The result of these steps is a local folder named sdk-sample-apps
.
Step 2. Configure the sample app
In this section you open the sample project in Android Studio, and view the integration points in the TODO pane.
You’ll visit each integration point in the sample app to understand how to complete a DaVinci flow, including handling the different nodes and their collectors, obtaining an access token and user information, and finally signing out of the session.
-
In Android Studio, click Open, navigate to the
sdk-sample-apps/android/kotlin-davinci
folder that has the downloaded sample source code in, and then click Open.Android Studio opens and loads the DaVinci tutorial project.
-
In the Project pane, navigate to samples > app.
-
On the View menu, select Tool Windows, and then click TODO.
The sample code is annotated with
TODO
comments to help you locate the integration points, where code changes are required.Figure 4. Integration points annotated withTODO
comments -
In the TODO pane, double-click the STEP 1 line.
Android Studio opens
DaVinciViewModel.kt
:DaVinciViewModel.kt
//TODO: Integration Point. STEP 1 val daVinci = DaVinci { logger = Logger.STANDARD // Oidc as module module(Oidc) { clientId = "<Client ID>" discoveryEndpoint = "<Discovery Endpoint>" scopes = mutableSetOf("<scope1>", "<scope2>", "…") redirectUri = "<Redirect URI>" } }
kotlinThis snippet initializes the
DaVinci
module, and leverages the OpenID Connect (OIDC) module to configure the settings to connect to your PingOne instance.-
Replace <Client ID> with the ID of the client you are connecting to in PingOne.
Example:
clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
Refer to Get configuration values from PingOne for instructions of where to find this value.
-
Replace <Discovery Endpoint> with the OIDC Discovery Endpoint value from the client you are connecting to in PingOne.
Example:
discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
Refer to Get configuration values from PingOne for instructions of where to find this value.
-
In the
scopes
property, add the scopes you want to assign users who complete authentication using the client.Example:
scopes = mutableSetOf("openid", "email", "profile")
-
Replace <Redirect URI> with the application ID of your sample app, followed by
://oauth2redirect
.Example:
redirectUri = "org.forgerock.demo://oauth2redirect"
The
redirectUri
value you use must exactly match one of the Redirect URIs value you enter in the native OAuth 2.0 application you created earlier. -
Optionally, delete the
TODO
comment to remove it from the list.
The result resembles the following:
DaVinciViewModel.kt
val daVinci = DaVinci { logger = Logger.STANDARD // Oidc as module module(Oidc) { clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2" discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration" scopes = mutableSetOf("openid", "email", "profile") redirectUri = "org.forgerock.demo://oauth2redirect" } }
kotlin -
-
In the TODO pane, double-click the STEP 2 line.
Android Studio opens
DaVinciViewModel.kt
:DaVinciViewModel.kt
//TODO: Integration Point. STEP 2 // Start the DaVinci flow, next node from the flow will be returned // Update the state with the next node /* val next = daVinci.start() state.update { it.copy(prev = next, node = next) } */
kotlinThis snippet calls
start()
to start the DaVinci flow, and assigns the returned node to the variablenext
.It also updates the app’s state to store the response as both
prev
andnode
.-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 3 line.
Android Studio opens
DaVinciViewModel.kt
:DaVinciViewModel.kt
//TODO: Integration Point. STEP 3 // Continue the DaVinci flow, next node from the flow will be returned // Update the state with the next node /* val next = current.next() state.update { it.copy(prev = current, node = next) } */
kotlinThis snippet calls
next()
to continue the DaVinci flow, by proceeding to the next available node. It assigns the newly returned node to the variablenext
.It also updates the app’s state to store the new response as
node
, and the current node asprev
.-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 4 line.
Android Studio opens
DaVinci.kt
:DaVinci.kt
//TODO: Integration Point. STEP 4 // Render the current node depending on its type /* when (val node = state.node) { is ContinueNode → { Render(node = node, onNodeUpdated, onStart) { onNext(node) } } is FailureNode → { Log.e("DaVinci", node.cause.message, node.cause) Render(node = node) } is ErrorNode → { Render(node) // Render the previous node if (state.prev is ContinueNode) { Render(node = state.prev, onNodeUpdated, onStart) { onNext(state.prev) } } } is SuccessNode → { LaunchedEffect(true) { onSuccess?.let { onSuccess() } } } else → {} } */
kotlinThis snippet watches for a change in state, and takes an action based on the ode type returned by DaVinci.
For example, if it is a
FailureNode
log an error message. If the node is aContinueNode
that continues the flow, call the render function to display the necessary fields on the screen.-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 5 line.
Android Studio opens
ContinueNode.kt
:ContinueNode.kt
//TODO: Integration Point. STEP 5 // Intermediate step in the Davinci Flow. The ContinueNode is a node that is used to // continue the flow. It can have multiple collectors that are used to collect user input. // Render the UI for each collector that are part of the ContinueNode. /* continueNode.collectors.forEach { when (it) { is FlowCollector → { hasAction = true FlowButton(it, onNext) } is PasswordCollector → { Password(it, onNodeUpdated) } is SubmitCollector → { hasAction = true SubmitButton(it, onNext) } is TextCollector → Text(it, onNodeUpdated) } } */
kotlinThis snippet handles the various collectors that are returned by the current node.
Loop through all of the collectors in the node and render an appropriate field in your app.
For example, this snippet handles text and password fields, and two types of button, a submit and a flow type.
-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 6 line.
Android Studio opens
TokenViewModel.kt
:TokenViewModel.kt
//TODO: Integration Point. STEP 6 // Retrieve the access token /* User.user()?.let { when (val result = it.token()) { is Failure → { state.update { it.copy(token = null, error = result.value) } } is Success → { state.update { it.copy(token = result.value, error = null) } } } } ?: run { state.update { it.copy(token = null, error = null) } } */
kotlinThis snippet gets called when the flow reaches a
SuccessNode
and gets an access token on behalf of the authenticated user.-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 7 line.
Android Studio opens
UserProfileViewModel.kt
:UserProfileViewModel.kt
//TODO: Integration Point. STEP 7 // Retrieve the user info /* User.user()?.let { user → when (val result = user.userinfo(false)) { is Result.Failure → state.update { s → s.copy(user = null, error = result.value) } is Result.Success → state.update { s → s.copy(user = result.value, error = null) } } } */
kotlinThis snippet gets the user’s info from DaVinci, for example their preferred name for display within the sample app.
-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
-
In the TODO pane, double-click the STEP 8 line.
Android Studio opens
LogoutViewModel.kt
:LogoutViewModel.kt
//TODO: Integration Point. STEP 8 // logout the user /* User.user()?.logout() */
kotlinThis snippet calls the
logout()
function on theUser
object to sign the user out of the app, and end their session in DaVinci.-
Uncomment the highlighted text.
-
Optionally, delete the
TODO
comment to remove it from the list.
-
Step 3. Test the app
In the following procedure, you run the sample app that you configured in the previous step.
-
Add or connect a device to Android Studio.
Learn more about devices in Android Studio in the Android Developer documentation:
-
On the Run menu, click Run 'samples.app'.
Android Studio starts the sample application on the simulated or connected device.
The app automatically starts the DaVinci flow:
Figure 5. The DaVinci sample app first screen with fields and buttons. -
Optionally, to register a new identity in PingOne, tap the No Account? Register now! link.
This link is an example of a FlowButton
.The app displays the registration screen:
Figure 6. The DaVinci sample app registration screen.-
Enter the details of the new identity, and then click Save.
The app creates the new identity in PingOne and returns to the sign on screen.
-
-
Enter the username and password of a PingOne identity, and then click Sign On.
The app sends the credentials to PingOne for validation, and on success displays the user’s info:
Figure 7. The DaVinci sample app displaying user info -
Tap the menu () icon, and then tap Show Token.
The app shows the access token obtained on behalf of the user.
Figure 8. The DaVinci sample app displaying a user’s access token -
Tap the menu () icon, and then tap Logout.
The app revokes the existing tokens and ends the session in PingOne.
Troubleshooting
This section contains help if you encounter an issue running the sample code.
What can cause validation errors in the request?
When starting the app you might see an error message as follows:
The request could not be completed. One or more validation errors were in the request.

The Logcat pane in Android Studio often contains additional information about the error:
{
"id" : "2a72bf00-5f20-4b78-a7d0-ad8d95e9b11b",
"code" : "INVALID_DATA",
"message" : "The request could not be completed. One or more validation errors were in the request.",
"details" : [
{
"code" : "INVALID_VALUE",
"target" : "redirect_uri",
"message" : "Redirect URI mismatch"
}
]
}
In this case the cause is Redirect URI mismatch
.
Ensure that your redirectUri
value in com/pingidentity/samples/app/davinci/DaVinciViewModel.kt
exactly matches one of the values you entered in the Redirect URIs field in your OAuth 2.0 application in PingOne:

DaVinci flow tutorial for iOS
This tutorial walks you through updating a provided sample app so that it connects to a PingOne tenant to authenticate a user using the PingOne sign-on with sessions DaVinci flow.
This flow allows users to register, authenticate, and verify their email address with PingOne.
Before you begin
To successfully complete this tutorial refer to the prerequisites in this section.
The tutorial also requires a configured PingOne instance.
Compatibility
- PingOne
-
-
Your PingOne instance must have DaVinci enabled.
-
This tutorial only supports the PingOne sign-on with sessions DaVinci flow.
-
- DaVinci flows
-
Ensure your user interface nodes only employ these supported connectors and capabilities:
-
HTTP Connector
-
Custom HTML Template
-
Compatible fields within the Custom HTML Template capability are:
-
Text field: Lets the user enter text
-
Password field: Lets the user enter a password which cannot be read from the screen
-
Submit Button: Lets the user submit field data and proceed
-
Flow Button: Lets the user trigger a new process without submitting field data
Verify that your flow does not depend on any unsupported elements:
-
SKPolling components: SKPolling components cannot be processed by the SDK and should not be included.
-
Variable field names: You cannot use variables or other values from a node’s input schema in the names of any field used by the SDK.
-
Images: Images included in the flow cannot be passed to the SDK.
-
Additional text: Titles or other text cannot be passed to the SDK.
-
- iOS
-
This sample app is compatible with iOS 12 and later.
Prerequisites
- Xcode
-
You can download the latest version for free from https://developer.apple.com/xcode/.
Server configuration
You must configure your PingOne instance for use with the DaVinci client.
Ask your PingOne administrator to complete the following tasks:
-
Configure a DaVinci flow
-
Create a DaVinci application
-
Configure PingOne for DaVinci flow invocation
To learn how to complete these steps, refer to Launching a flow with a Ping SDK in the PingOne DaVinci documentation.
Step 1. Download the samples
To start this tutorial, you need to download the ForgeRock SDK sample apps repo, which contains the projects you will use.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
Use the URL to clone the repository to a suitable location.
For example, from the command-line you could run:
git clone https://github.com/ForgeRock/sdk-sample-apps.git.git
shell
-
The result of these steps is a local folder named sdk-sample-apps
.
Step 2. Configure the sample app
In this section you open the sample project in Xcode, and view the integration points in the TODO pane.
You’ll visit each integration point in the sample app to understand how to complete a DaVinci flow, including handling the different nodes and their collectors, obtaining an access token and user information, and finally signing out of the session.
-
In Xcode, on the File menu, click Open.
-
Navigate to the
sdk-sample-apps
folder you cloned in the previous step, navigate toiOS
>swiftui-davinci
>Davinci.xcworkspace
, and then click Open.Xcode opens and loads the DaVinci tutorial project.
-
Open
DavinciViewModel
and locate theDaVinci.createDaVinci
call:TheDaVinci.createDaVinci
call inDavinciViewModel
public let davinci = DaVinci.createDaVinci { config in //TODO: Provide here the Server configuration. Add the PingOne server Discovery Endpoint and the OAuth 2.0 client details config.module(OidcModule.config) { oidcValue in oidcValue.clientId = "Client ID" oidcValue.scopes = ["scope1", "scope2", "scope3"] oidcValue.redirectUri = "Redirect URI" oidcValue.discoveryEndpoint = "Discovery Endpoint" } }
kotlinThis snippet initializes the
DaVinci
module, and leverages the OpenID Connect (OIDC) module to configure the settings to connect to your PingOne instance.-
In the
oidcValue.clientId
property, enter the ID of the client you are connecting to in PingOne.Example:
clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
Refer to Get configuration values from PingOne for instructions of where to find this value.
-
In the
oidcValue.scopes
property, add the scopes you want to assign users who complete authentication using the client.Example:
scopes = mutableSetOf("openid", "email", "profile")
-
In the
oidcValue.redirectUri
property, enter the application ID of your sample app, followed by://oauth2redirect
.Example:
redirectUri = "org.forgerock.demo://oauth2redirect"
The
redirectUri
value you use must exactly match one of the Redirect URIs value you enter in the native OAuth 2.0 application you created earlier. -
In the
oidcValue.discoveryEndpoint
property, enter the OIDC Discovery Endpoint value from the client you are connecting to in PingOne.Example:
discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
Refer to Get configuration values from PingOne for instructions of where to find this value.
-
Optionally, delete the
TODO
comment to remove it from the list.
The result resembles the following:
DavinciViewModel
public let davinci = DaVinci.createDaVinci { config in //TODO: Provide here the Server configuration. Add the PingOne server Discovery Endpoint and the OAuth2.0 client details config.module(OidcModule.config) { oidcValue in oidcValue.clientId = "6c7eb89a-66e9-ab12-cd34-eeaf795650b2" oidcValue.scopes = ["openid", "email", "profile"] oidcValue.redirectUri = "org.forgerock.demo://oauth2redirect" oidcValue.discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration" } }
kotlin -
Step 3. Test the app
In the following procedure, you run the sample app that you configured in the previous step. The app performs a centralized login on your PingOne instance.
Log in as a demo user
-
In Xcode, select
.Xcode launches the sample app in the iPhone simulator.
Figure 11. The iOS DaVinci sample main menu -
In the sample app on the iPhone simulator, tap Launch Davinci.
The sample app launches the DaVinci flow configured in the OAuth 2.0 profile.
Figure 12. The DaVinci sample app first screen with fields and buttons. -
Optionally, to register a new identity in PingOne:
-
Tap the No Account? Register now! link.
This link is an example of a FlowButton
.The app displays the registration screen:
Figure 13. The DaVinci sample app registration screen. -
Enter the details of the new identity, and then click Save.
The app creates the new identity in PingOne and returns to the sign on screen.
-
-
Sign on as a demo user:
-
Name:
demo
-
Password:
Ch4ng3it!
If authentication is successful, the application displays the tokens issued by PingOne.
Figure 14. The token issues by the DaVinci flow -
-
Tap Davinci to go back to the main menu, and then tap User Info.
The app retrieves the user’s info from the
/userinfo
endpoint and displays it on screen:Figure 15. User info retrieved from PingOne -
Tap Davinci to go back to the main menu, and then tap Logout.
The sample app displays a logout button.
Figure 16. Logout button in the iOS sample app -
Tap Proceed to logout
The app revokes the existing tokens and ends the session in PingOne.
To verify the user is signed out:
-
In the PingOne administration console, navigate to Directory > Users.
-
Select the user you signed in as.
-
From the Sevices dropdown, select Authentication:
Figure 17. Checking a user’s sessions in PingOne.The Sessions section displays any existing sessions the user has, and whether they originate from a mobile device.
-
DaVinci flow tutorial for JavaScript
This tutorial walks you through updating a provided sample app so that it connects to a PingOne tenant to authenticate a user using the PingOne sign-on with sessions DaVinci flow.
This flow allows users to register, authenticate, and verify their email address with PingOne.
Before you begin
To successfully complete this tutorial refer to the prerequisites in this section.
The tutorial also requires a configured server.
Compatibility
- PingOne
-
-
Your PingOne instance must have DaVinci enabled.
-
This tutorial only supports the PingOne sign-on with sessions DaVinci flow.
-
- DaVinci flows
-
Ensure your user interface nodes only employ these supported connectors and capabilities:
-
HTTP Connector
-
Custom HTML Template
-
Compatible fields within the Custom HTML Template capability are:
-
Text field: Lets the user enter text
-
Password field: Lets the user enter a password which cannot be read from the screen
-
Submit Button: Lets the user submit field data and proceed
-
Flow Button: Lets the user trigger a new process without submitting field data
Verify that your flow does not depend on any unsupported elements:
-
SKPolling components: SKPolling components cannot be processed by the SDK and should not be included.
-
Variable field names: You cannot use variables or other values from a node’s input schema in the names of any field used by the SDK.
-
Images: Images included in the flow cannot be passed to the SDK.
-
Additional text: Titles or other text cannot be passed to the SDK.
-
Prerequisites
- Node and NPM
-
The SDK requires a minimum Node.js version of
18
, and is tested on versions18
and20
. To get a supported version of Node.js, refer to the Node.js download page.You will also need
npm
to build the code and run the samples.
Server configuration
You must configure your PingOne instance for use with the DaVinci client.
Ask your PingOne administrator to complete the following tasks:
-
Configure a DaVinci flow
-
Create a DaVinci application
-
Configure PingOne for DaVinci flow invocation
To learn how to complete these steps, refer to Launching a flow with a Ping SDK in the PingOne DaVinci documentation.
Step 1. Download the samples
To start this tutorial, you need to download the SDK sample apps repo, which contains the projects you will use.
-
In a web browser, navigate to the SDK Sample Apps repository.
-
Download the source code using one of the following methods:
- Download a ZIP file
-
-
Click Code, and then click Download ZIP.
-
Extract the contents of the downloaded ZIP file to a suitable location.
-
- Use a Git-compatible tool to clone the repo locally
-
-
Click Code, and then copy the HTTPS URL.
-
Use the URL to clone the repository to a suitable location.
For example, from the command-line you could run:
git clone https://github.com/ForgeRock/sdk-sample-apps.git
shell
-
The result of these steps is a local folder named sdk-sample-apps
.
Step 2. Install the dependencies
In the following procedure, you install the required modules and dependencies, including the Ping SDK for JavaScript.
-
In a terminal window, navigate to the
sdk-sample-apps/javascript/embedded-login-davinci
folder. -
To install the required packages, enter the following:
npm install
shellThe
npm
tool downloads the required packages, and places them inside anode_modules
folder.
Step 3. Configure connection properties
In this step, you configure the sample app to connect to the authentication tree/journey you created when setting up your server configuration.
-
Copy the
.env.example
file in thesdk-sample-apps/javascript/embedded-login-davinci
folder and save it with the name.env
within this same directory. -
Open the
.env
file and edit the property values to match the settings you configured in previous steps:VITE_SCOPE=$SCOPE VITE_WEB_OAUTH_CLIENT=$WEB_OAUTH_CLIENT VITE_WELLKNOWN_URL=$WELLKNOWN_URL
properties-
In the VITE_SCOPE property, enter the scopes you want to assign users who complete authentication using the client, separated by spaces.
Example:
VITE_SCOPE="openid profile email address"
-
In VITE_WEB_OAUTH_CLIENT, property, enter the ID of the client you are connecting to in PingOne.
Example:
VITE_WEB_OAUTH_CLIENT="6c7eb89a-66e9-ab12-cd34-eeaf795650b2"
Refer to Get configuration values from PingOne for instructions of where to find this value.
-
In the VITE_WELLKNOWN_URL property, enter the OIDC Discovery Endpoint value from the client you are connecting to in PingOne.
Example:
discoveryEndpoint = "https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
Refer to Get configuration values from PingOne for instructions of where to find this value.
The result resembles the following:
VITE_SCOPE="openid profile email address" VITE_WEB_OAUTH_CLIENT="sdkPublicClient" VITE_WELLKNOWN_URL="https://auth.pingone.ca/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration"
properties -
Step 4. Test the app
In the following procedure, you run the sample app that you configured in the previous step. The sample connects to your server and walks through the authentication journey you created in an earlier step.
After successful authentication, the sample obtains an OAuth 2.0 access token and displays the related user information.
-
In a terminal window, navigate to the
sdk-sample-apps/javascript/embedded-login-davinci
folder. -
Use
npm
to run the sample:npm run dev
shell -
In a web browser:
-
Ensure you are NOT currently logged into the PingOne instance.
If you are logged into the PingOne instance in the browser, the sample will not work.
Logout of the PingAM instance before you run the sample.
-
Navigate to the following URL:
http://localhost:5173
A form appears with "Username" and "Password" fields:
Figure 18. The login page of the JavaScript DaVinci client sample.
-
-
Optionally, to register a new identity in PingOne, tap the No Account? Register now! link.
This link is an example of a FlowButton
.The app displays the registration screen:
Figure 19. The DaVinci sample app registration screen.-
Enter the details of the new identity, and then click Save.
The app creates the new identity in PingOne and returns to the sign on screen.
-
-
Enter the username and password of a PingOne identity, and then click Sign On.
The app sends the credentials to PingOne for validation, and on success displays the user’s session info:
Figure 20. The DaVinci sample app displaying session info -
To get an access token for the user, click Get Tokens.
Figure 21. The DaVinci sample app displaying tokens -
To revoke the OAuth 2.0 token, click the Logout button.
The application calls the
/as/revoke
endpoint to revoke the OAuth 2.0 token, and returns to the sign-in form.