Ping SDKs

Configure DaVinci client for iOS properties

Applies to:

  • DaVinci client for Android

  • DaVinci client for iOS

  • DaVinci client for JavaScript

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.

  1. In Xcode, select your project and navigate to Package Dependencies.

  2. Click the sign, and add the Ping SDK for iOS repository, https://github.com/ForgeRock/forgerock-ios-sdk.git.

  3. 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:

Properties
Property Description Required?

discoveryEndpoint

Your PingOne server’s .well-known/openid-configuration endpoint.

Example:

https://auth.pingone.com/3072206d-c6ce-ch15-m0nd-f87e972c7cc3/as/.well-known/openid-configuration

Yes

clientId

The client_id of the OAuth 2.0 client profile to use.

For example, 6c7eb89a-66e9-ab12-cd34-eeaf795650b2

Yes

scopes

A set of scopes to request when performing an OAuth 2.0 authorization flow.

For example, "openid", "profile", "email", "address", "revoke".

Yes

redirectUri

The redirect_uri as configured in the OAuth 2.0 client profile.

This value must match a value configured in your OAuth 2.0 client.

For example, org.forgerock.demo://oauth2redirect.

Yes

timeout

A timeout, in seconds, for each request that communicates with the server.

Default is 30 seconds.

No

acrValues

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:

"d1210a6b0b2665dbaa5b652221badba2"

No

Example

The following shows an example DaVinci client configuration, using the underlying Oidc module:

Configure DaVinci client connection properties
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:

Start a DaVinci flow
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 a collector object, which describes the information it requires from the client.

SuccessNode

This type indicates the flow is complete, and authentication was successful.

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.

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.

You can use the helper functions to determine which node type the server has returned:

Determine 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.

Access collectors in a 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.

Continue a DaVinci flow using next()
let next = node.next()

You do not need to pass any parameters into the next method as the DaVinci client internally stores the updated object, ready to return to the PingOne server.

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.

Displaying the reason for an error
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 FailureNode type, which you cannot resubmit and must restart the entire flow.

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.

Handling receipt of a FailureNode type
let 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:

Handling receipt of a SuccessNode type
let 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:

ViewModel
//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

}
View
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
    }
}