Use when building iOS authentication with Ping Identity — scaffolds a complete SwiftUI + MVVM authentication flow using the Ping Orchestration iOS SDK against PingOne Advanced Identity Cloud (AIC) or PingAM. Handles Journey configuration, OIDC token exchange, dynamic callback rendering, device binding, FIDO2, and logout.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ping-orchestration-sdks:ping-orchestration-ios-journey-sdkThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold a complete authentication flow in an iOS app using SwiftUI, MVVM, and the Ping Orchestration iOS SDK (Journey module).
assets/AccessTokenView.swift.templateassets/AccessTokenViewModel.swift.templateassets/App.swift.templateassets/BooleanAttributeInputCallbackView.swift.templateassets/ChoiceCallbackView.swift.templateassets/ConfirmationCallbackView.swift.templateassets/ConsentMappingCallbackView.swift.templateassets/ContentView.swift.templateassets/CustomViews.swift.templateassets/DeviceBindingCallbackView.swift.templateassets/DeviceProfileCallbackView.swift.templateassets/DeviceSigningVerifierCallbackView.swift.templateassets/ErrorView.swift.templateassets/FidoAuthenticationCallbackView.swift.templateassets/FidoRegistrationCallbackView.swift.templateassets/IdpCallbackView.swift.templateassets/JourneyView.swift.templateassets/JourneyViewModel.swift.templateassets/KbaCreateCallbackView.swift.templateassets/LogOutView.swift.templateScaffold a complete authentication flow in an iOS app using SwiftUI, MVVM, and the Ping Orchestration iOS SDK (Journey module).
| Field | Value |
|---|---|
| Language | Swift |
| Framework | SwiftUI, Combine |
| SDK | Ping Orchestration iOS SDK (Journey module: PingJourney, PingOidc, PingOrchestrate) |
| Pattern | MVVM (Model-View-ViewModel) with @StateObject / @ObservedObject |
| Min iOS | 16.0 |
| Xcode | Latest recommended |
This skill adds a complete authentication flow to an iOS application using SwiftUI and the MVVM pattern. It uses the Ping Identity Orchestration iOS SDK (Journey module) to authenticate users against PingOne Advanced Identity Cloud (AIC) or PingAM.
The implementation covers:
@Published properties and @MainActorContinueNode, SuccessNode, FailureNode, ErrorNodePackage.swift dependencies, Info.plist entries, and all views/components.Add the Ping iOS SDK via Swift Package Manager in Xcode:
Package URL: https://github.com/ForgeRock/ping-ios-sdk
Required products:
// In Package.swift or Xcode > File > Add Package Dependencies
.package(url: "https://github.com/ForgeRock/ping-ios-sdk", from: "1.3.0")
// Products to include:
// - PingJourney (core Journey orchestration)
// - PingOidc (OIDC token management)
// - PingOrchestrate (base orchestration framework)
// - PingJourneyPlugin (callback infrastructure)
Optional products for advanced features:
// - PingBinding (Device Binding / Signing Verifier)
// - PingFido (FIDO2 / WebAuthn)
// - PingExternalIdP (External IdP base)
// - PingExternalIdPGoogle (Google Sign-In)
// - PingExternalIdPFacebook (Facebook Login)
// - PingExternalIdPApple (Sign in with Apple)
// - PingProtect (PingOne Protect threat signals)
// - PingDeviceProfile (Device profile collection)
// - PingReCaptchaEnterprise (reCAPTCHA Enterprise)
// - PingDeviceId (Device identifier)
// - PingTamperDetector (Jailbreak detection)
// - PingBrowser (In-app browser)
// - PingStorage (Secure storage)
// - PingLogger (Logging)
See Package.swift template for a complete example.
Configure CORS in your PingAM or AIC tenant:
| Setting | Value |
|---|---|
| Allowed Origins | Your app's redirect URI scheme |
| Allowed Methods | GET, POST |
| Allowed Headers | Content-Type, X-Requested-With, X-Requested-Platform, Accept-API-Version, Authorization |
| Allow Credentials | Enabled |
Create a native/mobile OAuth client in PingAM / AIC:
| Setting | Value |
|---|---|
| Client Type | Native / Public |
| Grant Types | Authorization Code |
| Token Endpoint Auth Method | none |
| Scopes | openid profile email (minimum) |
| Redirect URIs | Your app's custom URL scheme (e.g., myapp://callback) |
| Implicit Consent | Enabled |
Add the following to your Info.plist for URL scheme handling:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
If using Google Sign-In, add the reversed client ID URL scheme. If using Facebook, add the Facebook app ID and display name entries.
Agent instruction: Before generating any file, collect the values below from the user.
Step 0 — Determine use case: Ask the user whether they want a sample app (full scaffold including Xcode project structure,
Package.swift,Info.plist, navigation, and styled views) or existing app integration (only SDK integration files: JourneyViewModel, callback views, JourneyView). This affects what files are generated in later steps.Ask for all
requiredparameters up front in a single prompt. For optional parameters, show the default and ask whether the user wants to override it. Do not proceed to Step 1 until every required parameter has a non-empty value.
| Parameter | Required | Default | Description |
|---|---|---|---|
serverUrl | Yes | — | PingAM / AIC server URL (e.g., https://your-tenant.forgeblocks.com/am) |
realm | Yes | alpha | Authentication realm |
cookieName | No | iPlanetDirectoryPro | Session cookie name |
clientId | Yes | — | OAuth 2.0 Client ID registered in PingAM / AIC for this iOS app |
scopes | Yes | openid profile email | OAuth 2.0 scopes (as a Swift array of strings) |
redirectUri | Yes | — | OAuth 2.0 redirect URI (custom URL scheme, e.g., myapp://callback) |
discoveryEndpoint | Yes | — | OIDC discovery endpoint URL (e.g., https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration) |
journeyLogin | No | Login | Name of the login Journey/Tree to invoke |
journeyRegister | No | Registration | Name of the registration Journey/Tree to invoke |
Before I get started, I want to confirm — are you wanting me to help you create a sample app or
integrate with your existing iOS app?
Before I generate the files, I need a few details about your PingAM / AIC setup:
1. Server URL — What is your PingAM/AIC server URL?
e.g. https://your-tenant.forgeblocks.com/am
2. Realm — Which realm? (default: alpha)
3. Cookie Name — Session cookie name? (default: iPlanetDirectoryPro)
4. Client ID — What is the OAuth 2.0 Client ID for this iOS app?
5. Scopes — Which OAuth 2.0 scopes? (default: openid profile email)
6. Redirect URI — What is the redirect URI? (e.g., myapp://callback)
7. Discovery Endpoint — What is the OIDC discovery endpoint URL?
e.g. https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration
8. Login Journey — Which Journey/Tree for login? (default: Login)
9. Register Journey — Which Journey/Tree for registration? (default: Registration)
serverUrl must start with https:// and should not have a trailing /.realm must be non-empty (commonly alpha, bravo, or root).clientId must be non-empty.scopes does not include openid, prepend it automatically and warn the user.redirectUri must use a custom URL scheme (not http:// or https://), e.g., myapp://callback.discoveryEndpoint must start with https:// and end with /.well-known/openid-configuration.Agent instruction: Users often provide a base AM URL (e.g.,
https://your-tenant.forgeblocks.com/am) instead of the full discovery endpoint. If the provided URL does not end with/.well-known/openid-configuration:
- Ask the user which realm they are using (commonly
alphaorbravofor AIC).- Construct the discovery endpoint as:
<serverUrl>/oauth2/<realm>/.well-known/openid-configuration- Strip any trailing
/from the server URL before construction.- Confirm the constructed URL with the user before proceeding.
Create JourneyViewModel.swift — the central ViewModel that configures the Journey instance and manages the authentication flow. See JourneyViewModel.swift template.
The Journey instance is created with Journey.createJourney:
import PingJourney
import PingOrchestrate
import PingOidc
let journey = Journey.createJourney { config in
config.serverUrl = "<serverUrl>"
config.realm = "<realm>"
config.cookie = "<cookieName>"
config.module(PingJourney.OidcModule.config) { oidcConfig in
oidcConfig.clientId = "<clientId>"
oidcConfig.scopes = [<scopes>]
oidcConfig.redirectUri = "<redirectUri>"
oidcConfig.discoveryEndpoint = "<discoveryEndpoint>"
}
}
Agent instruction: Substitute all
<parameter>placeholders with values collected above.
See Journey SDK Reference for configuration details.
The JourneyViewModel manages the Journey flow state using @Published properties:
@MainActor
class JourneyViewModel: ObservableObject {
@Published var state: JourneyState = JourneyState()
@Published var isLoading: Bool = false
@Published var showJourneyNameInput: Bool = true
func startJourney(with journeyName: String) async { ... }
func next(node: ContinueNode) async { ... }
func refresh() { ... }
}
Key responsibilities:
ContinueNode.next()SuccessNode, FailureNode, and ErrorNode outcomesSee JourneyViewModel.swift template.
Create JourneyView.swift — the SwiftUI view that renders the current Journey node. See JourneyView.swift template.
The view:
ContinueNode → render callbacks, SuccessNode → navigate to token, ErrorNode/FailureNode → show errorstruct JourneyView: View {
@StateObject private var journeyViewModel = JourneyViewModel()
@Binding var path: [MenuItem]
var body: some View {
// Switch on journeyViewModel.state.node type
}
}
Create CallbackView (within JourneyView.swift) — dispatches each callback to its dedicated SwiftUI view using Swift switch on callback type.
// Inside JourneyNodeView
for callback in continueNode.callbacks {
switch callback {
case let nameCallback as NameCallback:
NameCallbackView(callback: nameCallback, onNodeUpdated: onNodeUpdated)
case let passwordCallback as PasswordCallback:
PasswordCallbackView(callback: passwordCallback, onNodeUpdated: onNodeUpdated)
// ... other callback types
default:
Text("Unsupported callback type")
}
}
See Callback Types Reference for the full list of supported callbacks.
Create individual SwiftUI views for each callback type. See callback view templates.
All callback types supported by the Ping iOS SDK:
Core Callbacks (PingJourney module):
| Callback Class | View | Description |
|---|---|---|
NameCallback | NameCallbackView | Text input for username |
PasswordCallback | PasswordCallbackView | Secure text input for password |
ValidatedUsernameCallback | ValidatedUsernameCallbackView | Username with policy validation |
ValidatedPasswordCallback | ValidatedPasswordCallbackView | Password with policy validation |
TextInputCallback | TextInputCallbackView | Generic text input (e.g., OTP) |
TextOutputCallback | TextOutputCallbackView | Display messages (info, warning, error) |
SuspendedTextOutputCallback | TextOutputCallbackView | Suspended journey (magic link) — reuses TextOutputCallbackView |
BooleanAttributeInputCallback | BooleanAttributeInputCallbackView | Toggle for boolean attributes |
NumberAttributeInputCallback | NumberAttributeInputCallbackView | Numeric input |
StringAttributeInputCallback | StringAttributeInputCallbackView | String attribute (email, name) with validation |
ChoiceCallback | ChoiceCallbackView | Picker for multiple choice selection |
ConfirmationCallback | ConfirmationCallbackView | Action buttons (Yes/No, OK/Cancel) |
KbaCreateCallback | KbaCreateCallbackView | Security question and answer |
TermsAndConditionsCallback | TermsAndConditionsCallbackView | Terms acceptance toggle |
ConsentMappingCallback | ConsentMappingCallbackView | Consent to share profile data |
PollingWaitCallback | PollingWaitCallbackView | Auto-advancing wait step |
HiddenValueCallback | EmptyView | Non-visual — hidden form value |
MetadataCallback | (handled internally) | Non-visual — specializes into FIDO2/Protect callbacks |
Optional Callbacks (additional modules):
| Callback Class | Module | View | Description |
|---|---|---|---|
SelectIdpCallback | PingExternalIdP | SelectIdpCallbackView | Social/external IdP selection |
IdpCallback | PingExternalIdP | IdpCallbackView | External IdP OAuth flow |
DeviceProfileCallback | PingDeviceProfile | DeviceProfileCallbackView | Auto-collects device metadata |
DeviceBindingCallback | PingBinding | DeviceBindingCallbackView | Binds device to user account |
DeviceSigningVerifierCallback | PingBinding | DeviceSigningVerifierCallbackView | Signs challenge with device key |
FidoRegistrationCallback | PingFido | FidoRegistrationCallbackView | FIDO2/Passkey registration |
FidoAuthenticationCallback | PingFido | FidoAuthenticationCallbackView | FIDO2/Passkey authentication |
PingOneProtectInitializeCallback | PingProtect | PingOneProtectInitializeCallbackView | Initializes PingOne Protect |
PingOneProtectEvaluationCallback | PingProtect | PingOneProtectEvaluationCallbackView | Evaluates threat signals |
ReCaptchaEnterpriseCallback | PingReCaptchaEnterprise | ReCaptchaEnterpriseCallbackView | reCAPTCHA Enterprise verification |
Each callback view follows this pattern:
callback.prompt)callback.name = text)onNodeUpdated() or onNext() as appropriateNote: Auto-advancing callbacks (
PollingWaitCallback,DeviceProfileCallback,DeviceBindingCallback,DeviceSigningVerifierCallback,FidoRegistrationCallback,FidoAuthenticationCallback,PingOneProtectInitializeCallback,PingOneProtectEvaluationCallback,ReCaptchaEnterpriseCallback) perform their operation in.onAppearand callonNext()automatically when complete.
Create shared utility views used across the app. See CustomViews.swift template and ErrorView.swift template.
NextButton — Reusable styled button for form submissionHeaderView / DescriptionView — Node header and description displayErrorView / ErrorNodeView / ErrorMessageView — Error state viewsCreate ContentView.swift — the main app view with navigation. See ContentView.swift template.
struct ContentView: View {
@State private var path: [MenuItem] = []
var body: some View {
NavigationStack(path: $path) {
// Menu items: Journey, Token, User Info, Logout, etc.
}
.navigationDestination(for: MenuItem.self) { item in
switch item {
case .journey: JourneyView(path: $path)
case .token: AccessTokenView(menuItem: item)
case .user: UserInfoView(menuItem: item)
case .logout: LogOutView(path: $path)
// ... other items
}
}
}
}
Create views for token display, user info, logout, and device management:
AccessTokenView — Displays the current access token. See AccessTokenView.swift template.UserInfoView — Displays user info from the OIDC userinfo endpoint. See UserInfoView.swift template.LogOutView — Ends the session and revokes tokens. See LogOutView.swift template.Create ViewModels for token, user info, and logout operations:
AccessTokenViewModel — Fetches the access token from the OIDC module. See AccessTokenViewModel.swift template.UserInfoViewModel — Fetches user info from the OIDC userinfo endpoint. See UserInfoViewModel.swift template.LogOutViewModel — Handles session revocation and logout. See LogOutViewModel.swift template.Create the @main App struct. See App.swift template.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Handle URL scheme callbacks (Google, Facebook, etc.)
}
}
}
}
Agent instruction: Watch for these patterns and warn the user.
| Mistake | Fix |
|---|---|
Using http:// for serverUrl | Always use https:// for the server URL |
| Forgetting to register optional callback modules | Import and register callbacks from PingBinding, PingFido, PingProtect, PingExternalIdP, PingDeviceProfile, PingReCaptchaEnterprise as needed |
Not handling ErrorNode vs FailureNode | ErrorNode = server-side errors (invalid credentials), FailureNode = SDK-side failures (network error). Handle both. |
Missing @MainActor on ViewModel | The JourneyViewModel must be annotated @MainActor since it updates @Published properties from async contexts |
Not calling onNext() after auto-advancing callbacks | Auto-advancing callbacks (DeviceBinding, FIDO, Protect, reCAPTCHA, DeviceProfile) must call onNext() after their async operation completes |
| Missing URL scheme in Info.plist | The custom URL scheme for OAuth redirect must be registered in Info.plist CFBundleURLTypes |
| Not configuring OIDC module | The PingJourney.OidcModule.config block is required for token exchange to work after SuccessNode |
Using ObservableObject without @StateObject or @ObservedObject | Use @StateObject for ownership, @ObservedObject for injection |
SuccessNode: using path.append(...) or path = [.someView] instead of path = [] | path.append skips the root home view entirely; path = [.someView] creates a new view instance that re-runs checkSession() and may see no user yet (timing), reverting to logged-out state. The correct pattern is path = [] — pop to root so the existing root view's onChange(of: path) triggers checkSession() against journey.user() and transitions to the logged-in state. |
| Importing optional modules not added as SPM products | Only import a module if it has been added as an SPM product in Xcode. Each optional import (PingBinding, PingFido, PingProtect, etc.) requires the corresponding product added under File > Add Package Dependencies. Importing a module not in the target produces "No such module" build errors. |
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub pingidentity/ping-sdk-agent-skills --plugin ping-orchestration-sdks