Orchestration SDKs

User profile self-service in iOS apps

PingOne Advanced Identity Cloud PingAM iOS

You can run additional authentication journeys with the Orchestration SDKs in the same way as the initial journey that authenticates the user.

The only difference is that the Orchestration SDK automatically attaches the existing session to subsequent outgoing calls.

Your app should iterate through the callbacks in the journey in the same way as it would for the initial authentication journey.

Step 1. Starting additional journeys

Use the start() method to start additional journeys after initial authentication:

Starting a user profile self-service journey on iOS
let node = journey.start("sdkProfileManagement") { journeyConfig in
    journeyConfig.forceAuth = true
    journeyConfig.noSession = true
}

When launching additional journeys, you can optionally add parameters to control how the journey runs:

forceAuth

Set this parameter to true to force traversal of an authentication journey, even if the user already has a valid session.

noSession

Set this parameter to true to prevent the authentication journey from issuing a new session token upon successful completion.

If you don’t set this parameter, the Orchestration SDK replaces the initial session token with the new one from the additional journey.

Replacing the session also resets its time-to-live values.

Step 2. Handling callbacks from a self-service journey

The self-service journey returns one or more callbacks to display and update the user’s profile.

Common callbacks you might encounter in a user profile self-service journey include the following:

Common user profile self-service callbacks
Callback Nodes Description

BooleanAttributeInputCallback

Collects a boolean-style confirmation, such as yes/no or true/false.

ChoiceCallback

Provides a list of choices and collects the selected choice.

ConfirmationCallback

Collects a boolean-style confirmation, such as yes/no or true/false with an optional "Cancel" choice.

ConsentMappingCallback

Provides profile attributes that require user consent and collects consent from the user.

KbaCreateCallback

Collects knowledge-based authentication (KBA) answers to questions defined in the user profile, or user-defined question and answer pairs.

NumberAttributeInputCallback

Collects a numeric attribute, such as size or age.

StringAttributeInputCallback

Collects string attributes, such as city names, telephone numbers, and postcodes.

TermsAndConditionsCallback

Displays the current terms and conditions and collects the user’s agreement to them.

You can access these using node.callbacks. Your app should iterate through the received callbacks and display the appropriate user interface to the user to update the values.

Handling user profile self-service callbacks
// Start the user profile self-service flow
let node = journey.start("sdkProfileManagement") { journeyConfig in
    journeyConfig.forceAuth = true
    journeyConfig.noSession = true
}

if let currentNode = node as? ContinueNode {
      currentNode.callbacks.forEach { callback in
          switch callback {
              case let booleanCallback as BooleanAttributeInputCallback:
                  BooleanAttributeInputCallbackView(callback: booleanCallback, onNodeUpdated: onNodeUpdated).id(booleanCallback.id)

              case let choiceCallback as ChoiceCallback:
                  ChoiceCallbackView(callback: choiceCallback, onNodeUpdated: onNodeUpdated).id(choiceCallback.id)

              case let confirmationCallback as ConfirmationCallback:
                  ConfirmationCallbackView(callback: confirmationCallback, onSelected: onNext).id(confirmationCallback.id)

              case let consentCallback as ConsentMappingCallback:
                  ConsentMappingCallbackView(callback: consentCallback, onNodeUpdated: onNodeUpdated).id(consentCallback.id)

              case let kbaCallback as KbaCreateCallback:
                  KbaCreateCallbackView(callback: kbaCallback, onNodeUpdated: onNodeUpdated).id(kbaCallback.id)

              case let numberCallback as NumberAttributeInputCallback:
                  NumberAttributeInputCallbackView(callback: numberCallback, onNodeUpdated: onNodeUpdated).id(numberCallback.id)

              case let stringCallback as StringAttributeInputCallback:
                  StringAttributeInputCallbackView(callback: stringCallback, onNodeUpdated: onNodeUpdated).id(stringCallback.id)

              case let termsCallback as TermsAndConditionsCallback:
                  TermsAndConditionsCallbackView(callback: termsCallback, onNodeUpdated: onNodeUpdated).id(termsCallback.id)

              case _ as HiddenValueCallback:
                  EmptyView()

              default:
                  Text("Unsupported callback type")
          }
     }

    case let failureNode as FailureNode:
        // ...
        break

    case let errorNode as ErrorNode:
        // ...
        break

    default:
        break
    }
}

Attribute collector callbacks will usually have a prompt property to describe the field, and a value field containing the current or default value.

Example view for StringAttributeInputCallback
struct StringAttributeInputCallbackView: View {
    let callback: StringAttributeInputCallback
    let onNodeUpdated: () -> Void

    @State var text: String = ""

    private var hasErrors: Bool {
        !callback.failedPolicies.isEmpty
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            TextField(
                callback.prompt,
                text: $text
            )
            .autocorrectionDisabled()
            .textInputAutocapitalization(.never)
            .padding()
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(hasErrors ? Color.red : Color.gray, lineWidth: 1)
            )
            .onAppear(perform: {
                text = callback.value
            })
            .onChange(of: text) { newValue in
                callback.value = newValue
            }
            .onSubmit {
                onNodeUpdated()
            }

            // Error message display
            if hasErrors {
                ErrorMessageView(errors: callback.failedPolicies.map({ $0.failedDescription(for: callback.prompt)}))
            }
        }
        .padding()
    }
}