mcpField
P
ping-orchestration-ios-sdk

ping-orchestration-ios-sdk

github.com/pingidentitySecurity146 installsUpdated 13w ago
Repository

About ping-orchestration-ios-sdk

A skill for generating Ping Orchestration iOS SDK authentication integrations or a complete Xcode sample app. It covers Journey, DaVinci, and OIDC Web flows, with configurable callback and collector support.

Key features

  1. 01Scaffold Xcode sample projects
  2. 02Generate SDK integration files
  3. 03Configure Journey, DaVinci, and OIDC Web
  4. 04Select callback and collector tiers
  5. 05Analyze Journey export JSON

Use cases

  1. 01Create an iOS authentication sample
  2. 02Add Ping SDK authentication to an existing app
  3. 03Tailor callback views to a Journey export

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/pingidentity/ping-sdk-agent-skills --skill ping-orchestration-ios-sdk

After installing, type/ping-orchestration-ios-sdkin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

assets/
App.swift.davinci.template
App.swift.journey.template
App.swift.oidc.template
AppDaVinci.swift.template
AppJourney.swift.template
AppOidc.swift.template
AuthenticatedView.swift.davinci.template
AuthenticatedView.swift.journey.template
AuthenticatedView.swift.oidc.template
assets/callbacks/
BooleanAttributeInputCallbackView.swift.template
ChoiceCallbackView.swift.template
ConfirmationCallbackView.swift.template
ConsentMappingCallbackView.swift.template
DeviceBindingCallbackView.swift.template
DeviceProfileCallbackView.swift.template
DeviceSigningVerifierCallbackView.swift.template
FidoAuthenticationCallbackView.swift.template
FidoRegistrationCallbackView.swift.template
IdpCallbackView.swift.template
KbaCreateCallbackView.swift.template
NameCallbackView.swift.template
NumberAttributeInputCallbackView.swift.template
PasswordCallbackView.swift.template
PingOneProtectEvaluationCallbackView.swift.template
PingOneProtectInitializeCallbackView.swift.template
PollingWaitCallbackView.swift.template
ReCaptchaEnterpriseCallbackView.swift.template
SelectIdpCallbackView.swift.template
StringAttributeInputCallbackView.swift.template
TermsAndConditionsCallbackView.swift.template

Details

From the project README

Skill Parameters

This skill accepts an optional argument string when invoked as /ping-orchestration-ios-sdk <args>.

No-arg Invocation — Wizard Mode

When invoked with no arguments (/ping-orchestration-ios-sdk), run the wizard:

Step W1 — Determine intent using AskUserQuestion:

"What would you like to do?"
Options:
  A) Build a new sample app      — scaffold a complete Xcode project with full Ping branding
  B) Integrate into existing app — generate only the SDK files to drop into your project
  C) Browse the reference guide  — show the full SDK reference
  D) Something else              — let me describe what I need
  • A / B → Step W1b, then Step W2
    • A: follow with Automated Project Creation (read assets/project-scaffolding.md)
    • B: generate SDK integration files only (no Xcode project scaffolding)
  • C → display the reference guide from "Ping SDK for iOS — Integration Guide" onward. Stop.
  • D → follow-up free-text question, then route accordingly.

Step W1b — Journey export offer (options A and B only).

Ask:

"Do you have a Journey export JSON file? If so, paste it or attach it and I'll analyse it to generate a sample that handles exactly the callbacks your Journey uses."

  • If provided: run Section 13 (Journey Export Analysis) before Step W2. Pre-populate journeyName, callbackTier, and serverUrl from the analysis results.
  • If not: continue to Step W2 as normal.

Step W2 — Collect configuration (required for A and B).

First determine flowType if not already known, then ask only the parameters relevant to that flow in a single AskUserQuestion call. Show defaults where they exist. Do not proceed until every required field has a value.

Common parameters (all flows):

ParameterRequiredDefaultDescription
appNameYes—App name used for Swift types, PingHeaderView title, and bundle ID. E.g. PingDemo
outputPathYes (option A only)—Directory to write the Xcode project (absolute path)
flowTypeYes—oidc-web · journey · davinci
clientIdYes—OAuth 2.0 Client ID
redirectUriYes—OAuth 2.0 redirect URI (custom scheme, e.g. myapp://callback)
discoveryEndpointYes—Full OIDC discovery URL ending in /.well-known/openid-configuration
scopesNoopenid profile emailOAuth 2.0 scopes, space-separated

Journey-only additional parameters:

ParameterRequiredDefaultDescription
serverUrlYes—PingAM/AIC server base URL (no trailing /)
realmNoalphaAuthentication realm
cookieNameNoiPlanetDirectoryProSession cookie name
journeyNameNoLoginJourney tree name
callbackTierYes—basic, standard, or full (see Section 12)

DaVinci-only additional parameters:

ParameterRequiredDefaultDescription
callbackTierYes—basic, standard, or full (see Section 12)
acrValuesNo(none)Optional ACR values string — omit entirely if not provided

OIDC Web-only additional parameters:

ParameterRequiredDefaultDescription
acrValuesNo(none)Optional ACR values string — omit entirely if not provided

Present callbackTier options (Journey and DaVinci only):

  • Basic — NameCallback, PasswordCallback, TextOutputCallback, ChoiceCallback (Journey) / TextCollector, PasswordCollector, SubmitCollector, LabelCollector, FlowCollector (DaVinci). Minimal dependencies.
  • Standard — Everything in Basic plus: attribute callbacks, FIDO, Protect, Device Binding/Profile, SelectIdp, T&C, KBA, PollingWait, ConsentMapping. No third-party social SDKs.
  • Full fat — Everything in Standard plus IdpCallback/IdpCollector/SocialCollectorView (social login) and ReCaptchaEnterpriseCallback. Requires Facebook, Google, and ReCaptcha Enterprise SPM dependencies.

Validation rules:

  • redirectUri must use a custom scheme (not http:// or https://).
  • discoveryEndpoint must start with https:// and end with /.well-known/openid-configuration. If the user provides a base AM URL, construct <serverUrl>/oauth2/<realm>/.well-known/openid-configuration and confirm.
  • scopes must include openid. If missing, prepend it and warn.
  • serverUrl (Journey only) must start with https:// and have no trailing /.

Step W3 — Confirm and generate. Summarise collected values in a short table, ask "Ready to generate — does this look right?", then proceed on confirmation.


Parameters (with-args invocation)
ParameterSyntaxPurpose
create-samplecreate-sample "<description>"Generate a complete runnable sample app
app-nameapp-name "<name>"Set the app name. Defaults to "MyApp" if omitted.
output-pathoutput-path "<path>"Write files to disk. If omitted, print inline.
create-sample "<description>"
  1. Analyse the description — identify flow type(s), modules, and implied screens.

  2. Journey export offer — if the flow type is journey (or could be), ask:

    "Do you have a Journey export JSON? I can analyse it and tailor the sample to exactly the callbacks your Journey uses." If provided, run Section 13 before proceeding. Use its output to determine callbackTier and screens.

  3. Ask one clarifying question only if the flow type is genuinely ambiguous. Collect required parameters for the detected flow type (see W2 table) in a single AskUserQuestion with an explicit "I can use placeholders" option.

  4. Resolve the app name from app-name or use "MyApp".

  5. Generate using the template files in assets/. Substitute all PLACEHOLDER_* tokens (see table below). Use @Observable @MainActor ViewModels — never ObservableObject/@Published. Apply Ping Identity branding (Section 11).

    OIDC Web flow — read and substitute: App.swift.oidc.template, AppOidc.swift.template, OidcLoginViewModel.swift.template, ContentView.swift.oidc.template, LoginView.swift.oidc.template, AuthenticatedView.swift.oidc.template, Theme.swift.template

    Journey flow — read and substitute: App.swift.journey.template, AppJourney.swift.template, JourneyLoginViewModel.swift.template, ContentView.swift.journey.template, LoginView.swift.journey.template, AuthenticatedView.swift.journey.template, ContinueNodeView.swift.journey.template, Theme.swift.template

    Then, for each callback in callbackTier, read and substitute the matching file from assets/callbacks/:

    • Basic: NameCallbackView, PasswordCallbackView, ChoiceCallbackView, TextOutputCallbackView
    • Standard: everything in Basic plus TextInputCallbackView, ValidatedUsernameCallbackView, ValidatedPasswordCallbackView, StringAttributeInputCallbackView, NumberAttributeInputCallbackView, BooleanAttributeInputCallbackView, ConfirmationCallbackView, TermsAndConditionsCallbackView, KbaCreateCallbackView, ConsentMappingCallbackView, PollingWaitCallbackView, SelectIdpCallbackView, FidoRegistrationCallbackView, FidoAuthenticationCallbackView, DeviceProfileCallbackView, DeviceBindingCallbackView, DeviceSigningVerifierCallbackView, PingOneProtectInitializeCallbackView, PingOneProtectEvaluationCallbackView
    • Full fat: everything in Standard plus IdpCallbackView, ReCaptchaEnterpriseCallbackView

    In ContinueNodeView.swift.journey.template, replace // PLACEHOLDER_CALLBACK_CASES with the appropriate switch cases for the tier, and replace // PLACEHOLDER_TIER_IMPORTS with the matching import statements. Replace // PLACEHOLDER_SELF_ADVANCING_TYPES with the self-advancing types for the tier.

    DaVinci flow — read and substitute: App.swift.davinci.template, AppDaVinci.swift.template, DaVinciLoginViewModel.swift.template, ContentView.swift.davinci.template, LoginView.swift.davinci.template, AuthenticatedView.swift.davinci.template, CollectorNodeView.swift.davinci.template, Theme.swift.template

    Then, for each collector in callbackTier, read and substitute the matching file from assets/collectors/:

    • Basic: TextCollectorView, PasswordCollectorView, SubmitCollectorView, LabelCollectorView, FlowCollectorView
    • Standard: everything in Basic plus MultiSelectCheckBoxView, MultiSelectComboBoxView, SingleSelectDropdownView, SingleSelectRadioView, PhoneNumberCollectorView, ProtectCollectorView, FidoRegistrationCollectorView, FidoAuthenticationCollectorView, DeviceRegistrationCollectorView, DeviceAuthenticationCollectorView
    • Full fat: everything in Standard plus SocialCollectorView

    In CollectorNodeView.swift.davinci.template, replace // PLACEHOLDER_COLLECTOR_CASES with the appropriate switch cases for the tier, and replace // PLACEHOLDER_TIER_IMPORTS and // PLACEHOLDER_SELF_ADVANCING_TYPES accordingly.

  6. Deliver:

    • With output-path: read assets/project-scaffolding.md and follow Steps 0–G exactly. For SPM products per flow type, see Step C in that document.
    • Without output-path: print every file inline with a // --- <Filename>.swift --- header.
Template Placeholders
PlaceholderReplaces with
PLACEHOLDER_APP_NAMEResolved app name (e.g. PingDemo)
PLACEHOLDER_APP_NAME_UPPERUpper-cased app name (e.g. PINGDEMO) — used in Keychain account key construction
PLACEHOLDER_CLIENT_IDclientId value
PLACEHOLDER_REDIRECT_URIredirectUri value
PLACEHOLDER_DISCOVERY_ENDPOINTdiscoveryEndpoint value
PLACEHOLDER_SCOPESScopes as comma-separated quoted strings for Set([...]) (e.g. "openid", "profile", "email")
PLACEHOLDER_SERVER_URLserverUrl (Journey/DaVinci only)
PLACEHOLDER_REALMrealm (Journey/DaVinci only)
PLACEHOLDER_COOKIE_NAMEcookieName (Journey/DaVinci only)
PLACEHOLDER_JOURNEY_NAMEjourneyName (Journey only, default "Login")
PLACEHOLDER_ACR_VALUES_LINEDaVinci/OIDC: entire oidcConfig.acrValues = "..." line — omit the line entirely if not provided
PLACEHOLDER_KEYCHAIN_ACCOUNTKeychain account key — use "<APP_NAME_UPPER>_ACCESS_TOKEN" pattern (e.g. "PINGDEMO_ACCESS_TOKEN")

When the user chose placeholder mode: use "yourClientId", "yourapp://callback", "https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration".

Automated Project Creation (when output-path is provided)

Read assets/project-scaffolding.md and follow Steps 0–G exactly. Key reminders:

  • Use deployment target 18.0.
  • Layout is flat — .xcodeproj, source dir, and Config/ are siblings directly under <output-path>/.
  • PBXFileSystemSynchronizedRootGroup means Swift files dropped in the source dir are included automatically.
  • Use project.objects.each (not project.root_object.objects) to iterate all project objects in Ruby scripts.

Examples:

/ping-orchestration-ios-sdk create-sample "a simple username/password login using Journey"
/ping-orchestration-ios-sdk create-sample "OIDC centralized login with token display and sign-out" app-name "PingDemo" output-path "/Users/me/Dev"
/ping-orchestration-ios-sdk create-sample "Journey login with FIDO passkey registration and biometric auth" app-name "FidoSample"

Ping SDK for iOS — Integration Guide

Naming Conventions for Code Examples

Always use full, descriptive variable and parameter names. Never abbreviate: vm → viewModel, cb → nameCallback, cfg → journeyConfig. This applies to local bindings in switch statements and closure arguments (never use $0).

Flow Types — Choose Your Integration Path

Journey (native/embedded) — Authentication UI rendered entirely inside your app using SwiftUI views. No browser launched. onOpenURL / OpenURLMonitor are not needed.

OIDC Web (OidcWebClient) — Uses SFSafariViewController or ASWebAuthenticationSession. Must wire onOpenURL → OpenURLMonitor.shared.handleOpenURL(url) and handle foreground resumption.

DaVinci — Like Journey (native), but uses collectors instead of callbacks. Social IdP collectors (IdpCollector) do launch a browser, so wire onOpenURL if your DaVinci flow uses them.

Module Map

Pick only what you need — the SDK is modular:

ModuleImportWhen to add
PingOrchestrateCore engine — Node, ContinueNode, SuccessNode, FailureNode, ErrorNodeAlways (import explicitly in any file using node types)
PingJourneyPluginCallback protocolAny file that declares any Callback parameters
PingJourneyimport PingJourneyPingOne AIC / AM Journey flows, all concrete callback types
PingDavinciimport PingDavinciPingOne DaVinci flows
PingDavinciPluginimport PingDavinciPluginCollector protocol — any file using any Collector or switching on collector types
PingOidcimport PingOidcOIDC / OAuth token handling, User / userinfo, OidcWebClient
PingBrowserimport PingBrowserCentralized login — BrowserType, BrowserMode, BrowserLauncher, OpenURLMonitor
PingLoggerimport PingLoggerUnified logging
PingStorageimport PingStorageKeychain token/session storage
PingExternalIdPimport PingExternalIdPSocial login (Apple/Google/Facebook)
PingFidoimport PingFidoFIDO2 / WebAuthn
PingProtectimport PingProtectPingOne Protect risk signals
PingBindingimport PingBindingDevice binding & signing
PingOathimport PingOathTOTP / HOTP MFA
PingPushimport PingPushPush notification MFA

1 — SDK Package Reference

.package(url: "https://github.com/ForgeRock/ping-ios-sdk", from: "2.0.0")
// Add only the products your target needs:
.product(name: "PingJourney", package: "ping-ios-sdk"),
.product(name: "PingOidc", package: "ping-ios-sdk"),
.product(name: "PingBrowser", package: "ping-ios-sdk"),   // OidcWebClient flows

CocoaPods:

pod 'PingJourney'
pod 'PingStorage'
pod 'PingLogger'

2 — Node Type Reference

// PingOrchestrate — import in any file pattern-matching on nodes

struct FailureNode: Node {
    let cause: Error          // NON-OPTIONAL — use cause.localizedDescription
}
struct ErrorNode: Node {
    let message: String       // NON-OPTIONAL
    let status: Int?
}
struct SuccessNode: Node {
    let session: Session      // NON-OPTIONAL
}
// ContinueNode — more input needed (callbacks for Journey, collectors for DaVinci)

Node is NOT Equatable — track state changes via separate Bool flags:

var isRegistered = false   // set in next() based on returned node type

3 — Callback Property Types

Journey callback properties are non-optional Strings. Do not use ?? fallback. Basic-tier properties: NameCallback.name/prompt, PasswordCallback.password/prompt, TextInputCallback.text/prompt, ChoiceCallback.selectedIndex/choices, TextOutputCallback.message/messageType.

Standard-tier gotchas (compile errors if guessed wrong): ValidatedUsernameCallback → .username (NOT .value); ValidatedPasswordCallback → .password; ConfirmationCallback → .options/.selectedIndex: Int?; TermsAndConditionsCallback → .accepted: Bool; KbaCreateCallback → .selectedQuestion/.selectedAnswer; SelectIdpCallback → .value = provider.provider (no .setProvider()).

Read references/journey-callbacks.md for the complete Critical API Facts section before writing any Standard-tier callback view.

4 — AppJourney Singleton

import PingJourney
import PingOrchestrate
import PingLogger

@MainActor
class AppJourney {
    static let shared = AppJourney()

    let journey: Journey

    private init() {
        journey = Journey.createJourney { journeyConfig in
            journeyConfig.serverUrl = "https://your-server.example.com/am"
            journeyConfig.realm     = "alpha"
            journeyConfig.cookie    = "your-cookie-name"
            journeyConfig.logger    = LogManager.standard
            journeyConfig.module(PingJourney.OidcModule.config) { oidcConfig in
                oidcConfig.clientId          = "yourClientId"
                oidcConfig.scopes            = ["openid", "profile", "email"]
                oidcConfig.redirectUri       = "yourapp://callback"
                oidcConfig.discoveryEndpoint = "https://your-server.example.com/am/oauth2/alpha/.well-known/openid-configuration"
                oidcConfig.logger            = LogManager.standard
            }
        }
    }
}

The journeyName collected in the wizard is passed to journey.start() at call time:

node = await AppJourney.shared.journey.start(journeyName)

For multi-flow apps (Journey + DaVinci + OIDC Web) and token storage customisation, see references/advanced-patterns.md.

5 — Journey Authentication Flow

ViewModel pattern
import Foundation
import Observation
import PingJourney
import PingOrchestrate

@Observable
@MainActor
final class LoginViewModel {
    var node: Node?
    var isLoading = false

    let journeyName: String

    init(journeyName: String = "Login") {
        self.journeyName = journeyName
    }

    func start() async {
        isLoading = true
        defer { isLoading = false }
        node = await AppJourney.shared.journey.start(journeyName)
    }

    func next(continueNode: ContinueNode) async {
        isLoading = true
        defer { isLoading = false }
        node = await continueNode.next()
    }

    func reset() { node = nil }
}

Own the model with @State, not @StateObject:

@State private var loginViewModel = LoginViewModel()
Top-Level Node Switch
import PingOrchestrate
import PingJourney

switch loginViewModel.node {
case let successNode as SuccessNode:
    AuthenticatedView(successNode: successNode, onSignOut: loginViewModel.reset)
case let failureNode as FailureNode:
    ErrorView(message: failureNode.cause.localizedDescription, onRetry: retry)
case let errorNode as ErrorNode:
    ErrorView(
        message: errorNode.message.isEmpty ? "An error occurred." : errorNode.message,
        onRetry: retry
    )
case let continueNode as ContinueNode:
    LoginView(continueNode: continueNode, isLoading: loginViewModel.isLoading,
              onNext: { Task { await loginViewModel.next(continueNode: continueNode) } })
default:
    LoginView(continueNode: nil, isLoading: loginViewModel.isLoading,
              onNext: { Task { await loginViewModel.start() } })
}
Callback Renderer (ContinueNodeView)

Callback lives in PingJourneyPlugin — import it alongside PingOrchestrate. Use any Callback in function signatures (Swift 6 existential syntax). Suppress the Next button when hasSelfAdvancingCallback (FIDO, Confirmation, Protect, SelectIdp, DeviceBinding callbacks advance themselves).

Use assets/ContinueNodeView.swift.journey.template as the full implementation reference. Always read references/journey-callbacks.md before writing any callback view code.

6 — Session Lifecycle

let user: User? = await AppJourney.shared.journey.journeyUser()

if let user = user {
    let result = await user.userinfo(cache: false)
    switch result {
    case .success(let info):
        info.forEach { key, value in print("\(key): \(String(describing: value))") }
    case .failure(let error):
        print(error.localizedDescription)
    }
}

_ = await AppJourney.shared.journey.signOff()

7 — App Entry Point Integration

Journey / DaVinci native flows — no onOpenURL needed:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

OIDC Web and social IdP callbacks — three required wiring points:

import PingBrowser

@main
struct MyApp: App {
    @Environment(\.scenePhase) private var scenePhase

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    OpenURLMonitor.shared.handleOpenURL(url)
                }
                .onChange(of: scenePhase) { _, phase in
                    if phase == .active,
                       BrowserLauncher.currentBrowser.isInProgress {
                        BrowserLauncher.currentBrowser.handleAppActivation()
                    }
                }
        }
    }
}

URL scheme registration — in scaffold projects using GENERATE_INFOPLIST_FILE = YES, use the Ruby snippet (Step D in assets/project-scaffolding.md). Do not hand-author Info.plist (causes Multiple commands produce Info.plist) or use xcconfig bracketed syntax (causes error: expected to find '=' in macro condition).

8 — OIDC Web Flow (OidcWebClient)

Choose OIDC Web when the IDP hosts the login page. Choose Journey native (Section 5) when you need credential fields in your app's SwiftUI views.

authorize() is async throws and returns Result<User, OidcError>. Two error channels — throws for network/OIDC discovery failures and Result.failure for authorization errors. Always handle both in a do/catch wrapping a switch result.

For the AppOidc singleton, OidcWebClientConfig API, OidcError cases, BrowserType trade-offs, OidcLoginViewModel, and branded OidcLoginView — read references/oidc-web-reference.md.

9 — DaVinci Flow

DaVinci uses collectors instead of callbacks. The @Observable ViewModel pattern is identical to Journey. Replace PingJourney.OidcModule with PingDavinci.OidcModule. DaVinci does not use serverUrl, realm, or cookieName.

hasSelfAdvancingCollector types: SubmitCollector, FlowCollector, FidoRegistrationCollector, FidoAuthenticationCollector, DeviceRegistrationCollector, DeviceAuthenticationCollector.

Critical: always set submitCollector.value = submitCollector.id and flowCollector.value = flowCollector.id before calling next() — these drive the actionKey and eventType in the POST body.

For the AppDaVinci singleton, full CollectorNodeView, individual collector views, DaVinciAuthenticatedView, and session lifecycle — read references/davinci-collectors.md.

10 — Swift 6 Notes

  • Mark ViewModels @MainActor — correct isolation for @Observable properties.
  • Use Task { } inside @MainActor code to bridge into async SDK calls.
  • Write any Callback / any Collector in function signatures — required by Swift 6 existential syntax.
  • Node is not Equatable. Track state transitions via Bool flags, not .onChange(of: node).
  • Always add import Foundation to @Observable classes that use UserDefaults.

11 — Ping Identity Visual Branding

Write assets/Theme.swift.template as-is to <AppName>/Theme.swift (substitute PLACEHOLDER_APP_NAME). Contains: PingPrimaryButtonStyle, PingHeaderView, PingTextField, PingSecureField, FidoIconView, ErrorMessageView, LoadingOverlay, ErrorView. Key tokens: Color.pingRed = #A31300, corner radius 15pt (buttons) / 8pt (fields), header padding 32pt, content padding 24pt.

Copy assets/Logo.imageset/ into Assets.xcassets/Logo.imageset/ and reference with Image("Logo"). Apply .tint(.pingRed) to any Form.

12 — Callback / Collector Tiers

Full tier breakdown with decision guide: references/callback-tiers.md

Summary: Basic (username/password/choice, no extra SPM) → Standard (adds FIDO, Protect, device binding, T&C, KBA, social IdP browser-based) → Full fat (adds native Apple/Google/Facebook social + ReCaptcha Enterprise). The wizard's callbackTier parameter maps directly to these tiers.

Common Pitfalls

See references/common-pitfalls.md — covers Swift 6 errors, OIDC redirect issues, DaVinci collector gotchas, SPM build errors, and simulator problems.

13 — Journey Export Analysis

When the user provides a Journey export JSON, run the full analysis in references/journey-export-analysis.md before writing any code (steps A1–A8: enumerate trees, map nodes→callbacks, inspect scripts, determine tier, extract config).

Comments

Markdown supported