diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.github/copilot-instructions.md b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.github/copilot-instructions.md
new file mode 100644
index 00000000..8e82ed24
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.github/copilot-instructions.md
@@ -0,0 +1,14 @@
+# Copilot Custom Instructions
+
+- This repository uses Swift 6.1+ and SwiftUI for iOS 18+ apps. All code should follow modern Swift and SwiftUI best practices.
+- This is an iOS project NOT a pure Swift Package or macOS project. It utlises a local Swift Package which is wrapped in an Xcode project. This makes it easier for agents to work on the project.
+- Use the Model-View (MV) pattern with native SwiftUI state management (`@State`, `@Observable`, `@Environment`, `@Binding`). Do not use ViewModels or MVVM.
+- All concurrency must use Swift Concurrency (async/await, actors, @MainActor). Do not use GCD or completion handlers.
+- Write all new code and features inside the Swift Package (`YourAppPackage`), not in the app shell.
+- Use the Swift Testing framework (`@Test`, `#expect`, `#require`) for all tests. Place tests in the package's `Tests/` directory.
+- When running tests use the `test_sim_name_ws` tool do not use `swift_package_test`.-
+- Use XcodeBuildMCP tools for building, testing, and automation. Prefer these over raw xcodebuild or CLI commands.
+- For data persistence, use SwiftData (never CoreData), though only use for complex scenarios, prefer simpler options first e.g. UserDefaults.
+- Always provide accessibility labels and identifiers for UI elements.
+- Never log sensitive information or use insecure network calls.
+- For full style, architecture, and workflow details, refer to the project documentation in [`template/.cursor/rules/`](../.cursor/rules/).
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.gitignore b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.gitignore
new file mode 100644
index 00000000..fda4de3a
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/.gitignore
@@ -0,0 +1,62 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## Obj-C/Swift specific
+*.hmap
+
+## App packaging
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
+
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+# Package.pins
+# Package.resolved
+# *.xcodeproj
+#
+# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
+# hence it is not needed unless you have added a package configuration file to your project
+# .swiftpm
+
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+# *.xcworkspace
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build/
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
\ No newline at end of file
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Debug.xcconfig b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Debug.xcconfig
new file mode 100644
index 00000000..75b2eb20
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Debug.xcconfig
@@ -0,0 +1,8 @@
+// Debug.xcconfig
+// Debug configuration for iOS projects - minimal overrides only
+// Generated by XcodeBuildMCP
+
+#include "Shared.xcconfig"
+
+// No additional debug-specific overrides needed
+// All debug settings use Xcode project defaults
\ No newline at end of file
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/PasskeysSwiftUI.entitlements b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/PasskeysSwiftUI.entitlements
new file mode 100644
index 00000000..06ee6669
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/PasskeysSwiftUI.entitlements
@@ -0,0 +1,11 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ webcredentials:your-server-domain.com?mode=develop
+ webcredentials:your-server-domain.com
+
+
+
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Release.xcconfig b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Release.xcconfig
new file mode 100644
index 00000000..67bf1f04
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Release.xcconfig
@@ -0,0 +1,8 @@
+// Release.xcconfig
+// Release configuration for iOS projects - minimal overrides only
+// Generated by XcodeBuildMCP
+
+#include "Shared.xcconfig"
+
+// No additional release-specific overrides needed
+// All release settings use Xcode project defaults
\ No newline at end of file
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Shared.xcconfig b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Shared.xcconfig
new file mode 100644
index 00000000..e41ee57a
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Shared.xcconfig
@@ -0,0 +1,39 @@
+// Shared.xcconfig
+// Minimal shared configuration for scaffold tool customization
+// All other settings use Xcode project defaults
+// Generated by XcodeBuildMCP
+
+// ==========================================
+// Project Identity
+// ==========================================
+PRODUCT_NAME = PasskeysSwiftUI
+PRODUCT_DISPLAY_NAME = PasskeysSwiftUI
+PRODUCT_BUNDLE_IDENTIFIER = com.pingidentity.passkeysswiftui
+MARKETING_VERSION = 1.0.0
+CURRENT_PROJECT_VERSION = 1
+
+// ==========================================
+// Platform Configuration
+// ==========================================
+IPHONEOS_DEPLOYMENT_TARGET = 18.0
+
+// (1 == iPhone, 2 == iPad)
+TARGETED_DEVICE_FAMILY = 1,2
+
+// ==========================================
+// Info PLIST
+// ==========================================
+GENERATE_INFOPLIST_FILE = YES
+INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait
+INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
+
+// ==========================================
+// Info PLIST Keys
+// ==========================================
+INFOPLIST_KEY_NSFaceIDUsageDescription = This app uses Face ID for biometric authentication with passkeys.
+
+// ==========================================
+// Entitlements
+// ==========================================
+// AI agents can modify Config/PasskeysSwiftUI.entitlements to add capabilities
+CODE_SIGN_ENTITLEMENTS = Config/PasskeysSwiftUI.entitlements
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Tests.xcconfig b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Tests.xcconfig
new file mode 100644
index 00000000..49318f6b
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/Config/Tests.xcconfig
@@ -0,0 +1,14 @@
+// Tests.xcconfig
+// Test configuration for iOS projects - minimal overrides only
+// Generated by XcodeBuildMCP
+
+#include "Shared.xcconfig"
+
+// ==========================================
+// Test Target Settings (Customizable by scaffold tool)
+// ==========================================
+PRODUCT_BUNDLE_IDENTIFIER = com.pingidentity.passkeysswiftui
+TEST_TARGET_NAME = PasskeysSwiftUI
+
+// Fix duplicate module name issue
+PRODUCT_MODULE_NAME = $(PRODUCT_NAME)UITests
\ No newline at end of file
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.pbxproj b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..8f15acbd
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.pbxproj
@@ -0,0 +1,538 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 43D0BE5DEADE4B9F1BB3EB28 /* PingJourney in Frameworks */ = {isa = PBXBuildFile; productRef = 81158D7A074F1B72DD4C07DE /* PingJourney */; };
+ 651E879EEB931228CEF2D31A /* PingOidc in Frameworks */ = {isa = PBXBuildFile; productRef = 06B04414648276D65C9BDF68 /* PingOidc */; };
+ 93D1A04D884C046026324BFC /* PingOrchestrate in Frameworks */ = {isa = PBXBuildFile; productRef = 4A04B7FD6BA345628F49F01E /* PingOrchestrate */; };
+ 9CF36A7813727C9D8D162716 /* PingJourneyPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 62EB33A1CFDCF3A274C93942 /* PingJourneyPlugin */; };
+ EC9C5CCB67FD2FC3CA415125 /* PingLogger in Frameworks */ = {isa = PBXBuildFile; productRef = E256473691CDBA1288A61EF4 /* PingLogger */; };
+ EE1BA5E17E12F97795B4A26B /* PingFido in Frameworks */ = {isa = PBXBuildFile; productRef = 9076D702DE7ED6A5139E4106 /* PingFido */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 8B41F65D2DEDD0D6001A66F9 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 8B41F63D2DEDD0D5001A66F9 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 8B41F6442DEDD0D5001A66F9;
+ remoteInfo = PasskeysSwiftUI;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 8B41F6452DEDD0D5001A66F9 /* PasskeysSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PasskeysSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 8B41F65C2DEDD0D6001A66F9 /* PasskeysSwiftUI.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PasskeysSwiftUI.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 8BD71C0A2DEE41E000CEDD92 /* Exceptions for "Config" folder in "PasskeysSwiftUI" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Debug.xcconfig,
+ Release.xcconfig,
+ Shared.xcconfig,
+ Tests.xcconfig,
+ );
+ target = 8B41F6442DEDD0D5001A66F9 /* PasskeysSwiftUI */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 8B41F6472DEDD0D5001A66F9 /* PasskeysSwiftUI */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = PasskeysSwiftUI;
+ sourceTree = "";
+ };
+ 8B41F65F2DEDD0D6001A66F9 /* PasskeysSwiftUIUITests */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = PasskeysSwiftUIUITests;
+ sourceTree = "";
+ };
+ 8BD71C052DEE41D800CEDD92 /* Config */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 8BD71C0A2DEE41E000CEDD92 /* Exceptions for "Config" folder in "PasskeysSwiftUI" target */,
+ );
+ path = Config;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 8B41F6422DEDD0D5001A66F9 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 43D0BE5DEADE4B9F1BB3EB28 /* PingJourney in Frameworks */,
+ EE1BA5E17E12F97795B4A26B /* PingFido in Frameworks */,
+ 651E879EEB931228CEF2D31A /* PingOidc in Frameworks */,
+ 93D1A04D884C046026324BFC /* PingOrchestrate in Frameworks */,
+ EC9C5CCB67FD2FC3CA415125 /* PingLogger in Frameworks */,
+ 9CF36A7813727C9D8D162716 /* PingJourneyPlugin in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8B41F6592DEDD0D6001A66F9 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 8B41F63C2DEDD0D5001A66F9 = {
+ isa = PBXGroup;
+ children = (
+ 8BD71C052DEE41D800CEDD92 /* Config */,
+ 8B41F6472DEDD0D5001A66F9 /* PasskeysSwiftUI */,
+ 8B41F65F2DEDD0D6001A66F9 /* PasskeysSwiftUIUITests */,
+ 8B41F6812DEDD23B001A66F9 /* Frameworks */,
+ 8B41F6462DEDD0D5001A66F9 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 8B41F6462DEDD0D5001A66F9 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 8B41F6452DEDD0D5001A66F9 /* PasskeysSwiftUI.app */,
+ 8B41F65C2DEDD0D6001A66F9 /* PasskeysSwiftUI.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 8B41F6812DEDD23B001A66F9 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 8B41F6442DEDD0D5001A66F9 /* PasskeysSwiftUI */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 8B41F6662DEDD0D6001A66F9 /* Build configuration list for PBXNativeTarget "PasskeysSwiftUI" */;
+ buildPhases = (
+ 8B41F6412DEDD0D5001A66F9 /* Sources */,
+ 8B41F6422DEDD0D5001A66F9 /* Frameworks */,
+ 8B41F6432DEDD0D5001A66F9 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 8B41F6472DEDD0D5001A66F9 /* PasskeysSwiftUI */,
+ 8BD71C052DEE41D800CEDD92 /* Config */,
+ );
+ name = PasskeysSwiftUI;
+ packageProductDependencies = (
+ 81158D7A074F1B72DD4C07DE /* PingJourney */,
+ 9076D702DE7ED6A5139E4106 /* PingFido */,
+ 06B04414648276D65C9BDF68 /* PingOidc */,
+ 4A04B7FD6BA345628F49F01E /* PingOrchestrate */,
+ E256473691CDBA1288A61EF4 /* PingLogger */,
+ 62EB33A1CFDCF3A274C93942 /* PingJourneyPlugin */,
+ );
+ productName = PasskeysSwiftUI;
+ productReference = 8B41F6452DEDD0D5001A66F9 /* PasskeysSwiftUI.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 8B41F65B2DEDD0D6001A66F9 /* PasskeysSwiftUIUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 8B41F66C2DEDD0D6001A66F9 /* Build configuration list for PBXNativeTarget "PasskeysSwiftUIUITests" */;
+ buildPhases = (
+ 8B41F6582DEDD0D6001A66F9 /* Sources */,
+ 8B41F6592DEDD0D6001A66F9 /* Frameworks */,
+ 8B41F65A2DEDD0D6001A66F9 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 8B41F65E2DEDD0D6001A66F9 /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ 8B41F65F2DEDD0D6001A66F9 /* PasskeysSwiftUIUITests */,
+ );
+ name = PasskeysSwiftUIUITests;
+ productName = PasskeysSwiftUIUITests;
+ productReference = 8B41F65C2DEDD0D6001A66F9 /* PasskeysSwiftUI.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 8B41F63D2DEDD0D5001A66F9 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1630;
+ LastUpgradeCheck = 1630;
+ TargetAttributes = {
+ 8B41F6442DEDD0D5001A66F9 = {
+ CreatedOnToolsVersion = 16.3;
+ };
+ 8B41F65B2DEDD0D6001A66F9 = {
+ CreatedOnToolsVersion = 16.3;
+ TestTargetID = 8B41F6442DEDD0D5001A66F9;
+ };
+ };
+ };
+ buildConfigurationList = 8B41F6402DEDD0D5001A66F9 /* Build configuration list for PBXProject "PasskeysSwiftUI" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 8B41F63C2DEDD0D5001A66F9;
+ minimizedProjectReferenceProxies = 1;
+ packageReferences = (
+ 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */,
+ );
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 8B41F6462DEDD0D5001A66F9 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 8B41F6442DEDD0D5001A66F9 /* PasskeysSwiftUI */,
+ 8B41F65B2DEDD0D6001A66F9 /* PasskeysSwiftUIUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 8B41F6432DEDD0D5001A66F9 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8B41F65A2DEDD0D6001A66F9 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 8B41F6412DEDD0D5001A66F9 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8B41F6582DEDD0D6001A66F9 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 8B41F65E2DEDD0D6001A66F9 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 8B41F6442DEDD0D5001A66F9 /* PasskeysSwiftUI */;
+ targetProxy = 8B41F65D2DEDD0D6001A66F9 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 8B41F6642DEDD0D6001A66F9 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.4;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 8B41F6652DEDD0D6001A66F9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.4;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_VERSION = 5.0;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 8B41F6672DEDD0D6001A66F9 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 8BD71C052DEE41D800CEDD92 /* Config */;
+ baseConfigurationReferenceRelativePath = Debug.xcconfig;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 9QSE66762D;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_CFBundleDisplayName = PasskeysSwiftUI;
+ INFOPLIST_KEY_NSFaceIDUsageDescription = "This app uses Face ID for biometric authentication with passkeys.";
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.unsummit;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 8B41F6682DEDD0D6001A66F9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 8BD71C052DEE41D800CEDD92 /* Config */;
+ baseConfigurationReferenceRelativePath = Release.xcconfig;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 9QSE66762D;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_CFBundleDisplayName = PasskeysSwiftUI;
+ INFOPLIST_KEY_NSFaceIDUsageDescription = "This app uses Face ID for biometric authentication with passkeys.";
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeRight UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.forgerock.unsummit;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 8B41F66D2DEDD0D6001A66F9 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 8BD71C052DEE41D800CEDD92 /* Config */;
+ baseConfigurationReferenceRelativePath = Tests.xcconfig;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ GENERATE_INFOPLIST_FILE = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = PasskeysSwiftUI;
+ };
+ name = Debug;
+ };
+ 8B41F66E2DEDD0D6001A66F9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 8BD71C052DEE41D800CEDD92 /* Config */;
+ baseConfigurationReferenceRelativePath = Tests.xcconfig;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ GENERATE_INFOPLIST_FILE = YES;
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = PasskeysSwiftUI;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 8B41F6402DEDD0D5001A66F9 /* Build configuration list for PBXProject "PasskeysSwiftUI" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8B41F6642DEDD0D6001A66F9 /* Debug */,
+ 8B41F6652DEDD0D6001A66F9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 8B41F6662DEDD0D6001A66F9 /* Build configuration list for PBXNativeTarget "PasskeysSwiftUI" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8B41F6672DEDD0D6001A66F9 /* Debug */,
+ 8B41F6682DEDD0D6001A66F9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 8B41F66C2DEDD0D6001A66F9 /* Build configuration list for PBXNativeTarget "PasskeysSwiftUIUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8B41F66D2DEDD0D6001A66F9 /* Debug */,
+ 8B41F66E2DEDD0D6001A66F9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/ForgeRock/ping-ios-sdk";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 2.0.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 06B04414648276D65C9BDF68 /* PingOidc */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingOidc;
+ };
+ 4A04B7FD6BA345628F49F01E /* PingOrchestrate */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingOrchestrate;
+ };
+ 62EB33A1CFDCF3A274C93942 /* PingJourneyPlugin */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingJourneyPlugin;
+ };
+ 81158D7A074F1B72DD4C07DE /* PingJourney */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingJourney;
+ };
+ 9076D702DE7ED6A5139E4106 /* PingFido */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingFido;
+ };
+ E256473691CDBA1288A61EF4 /* PingLogger */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 2E4B7E570803C45A04F1658C /* XCRemoteSwiftPackageReference "ping-ios-sdk" */;
+ productName = PingLogger;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 8B41F63D2DEDD0D5001A66F9 /* Project object */;
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..c1f9eb91
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,114 @@
+{
+ "originHash" : "956d622730eea977a9714ff7bca8e26297309c8b894a7bbf03c3f9f9b8d6d312",
+ "pins" : [
+ {
+ "identity" : "app-check",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/app-check.git",
+ "state" : {
+ "revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
+ "version" : "11.2.0"
+ }
+ },
+ {
+ "identity" : "appauth-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/openid/AppAuth-iOS.git",
+ "state" : {
+ "revision" : "145104f5ea9d58ae21b60add007c33c1cc0c948e",
+ "version" : "2.0.0"
+ }
+ },
+ {
+ "identity" : "facebook-ios-sdk",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/facebook/facebook-ios-sdk.git",
+ "state" : {
+ "revision" : "3fe31c168903759de1c5752d12856c5c437c6862",
+ "version" : "16.3.1"
+ }
+ },
+ {
+ "identity" : "googlesignin-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/GoogleSignIn-iOS.git",
+ "state" : {
+ "revision" : "3996d908c7b3ce8a87d39c808f9a6b2a08fbe043",
+ "version" : "9.0.0"
+ }
+ },
+ {
+ "identity" : "googleutilities",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/GoogleUtilities.git",
+ "state" : {
+ "revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
+ "version" : "8.1.0"
+ }
+ },
+ {
+ "identity" : "gtm-session-fetcher",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/gtm-session-fetcher.git",
+ "state" : {
+ "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
+ "version" : "3.5.0"
+ }
+ },
+ {
+ "identity" : "gtmappauth",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/GTMAppAuth.git",
+ "state" : {
+ "revision" : "56e0ccf09a6dd29dc7e68bdf729598240ca8aa16",
+ "version" : "5.0.0"
+ }
+ },
+ {
+ "identity" : "interop-ios-for-google-sdks",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/interop-ios-for-google-sdks.git",
+ "state" : {
+ "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
+ "version" : "101.0.0"
+ }
+ },
+ {
+ "identity" : "ping-ios-sdk",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ForgeRock/ping-ios-sdk",
+ "state" : {
+ "revision" : "d025874d598d7337a72f0a8ee446dcddae7b6ef2",
+ "version" : "2.0.0"
+ }
+ },
+ {
+ "identity" : "pingone-signals-sdk-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/pingidentity/pingone-signals-sdk-ios.git",
+ "state" : {
+ "revision" : "e41f7070fdbb43dd7762274ec610c099256a7c7e",
+ "version" : "5.4.0"
+ }
+ },
+ {
+ "identity" : "promises",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/google/promises.git",
+ "state" : {
+ "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
+ "version" : "2.4.0"
+ }
+ },
+ {
+ "identity" : "recaptcha-enterprise-mobile-sdk",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/GoogleCloudPlatform/recaptcha-enterprise-mobile-sdk.git",
+ "state" : {
+ "revision" : "fb634e89a36fd91725ad654d5576d800c061b37d",
+ "version" : "18.8.2"
+ }
+ }
+ ],
+ "version" : 3
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/xcshareddata/xcschemes/PasskeysSwiftUI.xcscheme b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/xcshareddata/xcschemes/PasskeysSwiftUI.xcscheme
new file mode 100644
index 00000000..6190055b
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcodeproj/xcshareddata/xcschemes/PasskeysSwiftUI.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/App/PasskeysApp.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/App/PasskeysApp.swift
new file mode 100644
index 00000000..ae9e3f43
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/App/PasskeysApp.swift
@@ -0,0 +1,20 @@
+//
+// PasskeysApp.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+
+@main
+struct PasskeysApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 00000000..eb878970
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..23058801
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,35 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Contents.json b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Contents.json b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Contents.json
new file mode 100644
index 00000000..1acb1006
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Ping Identity Logo.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Ping Identity Logo.png b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Ping Identity Logo.png
new file mode 100644
index 00000000..2218aa29
Binary files /dev/null and b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Assets.xcassets/Logo.imageset/Ping Identity Logo.png differ
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Config/AppConfiguration.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Config/AppConfiguration.swift
new file mode 100644
index 00000000..ef55f668
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Config/AppConfiguration.swift
@@ -0,0 +1,57 @@
+//
+// AppConfiguration.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import Foundation
+import PingJourney
+import PingStorage
+import PingLogger
+
+enum JourneyName {
+ static let login = "Login"
+ static let fidoRegistration = "BlogWebAuthnRegistration"
+ static let fidoAuthentication = "BlogWebAuthnAuthentication"
+}
+
+enum UserDefaultsKey {
+ static let biometricsEnabled = "BiometricsEnabled"
+}
+
+enum ServerConfig {
+ static let serverUrl = "<#YOUR_SERVER_URL#>" // e.g. "https://your-tenant.forgeblocks.com/am"
+ static let realm = "<#YOUR_REALM#>" // e.g. "alpha"
+ static let cookieName = "<#YOUR_COOKIE_NAME#>" // Session cookie name from your AM config
+ static let clientId = "<#YOUR_CLIENT_ID#>" // OAuth 2.0 client ID
+ static let scopes: Set = ["openid", "profile", "email", "address"]
+ static let redirectUri = "<#YOUR_REDIRECT_URI#>" // Must match your OAuth 2.0 client config
+ static let discoveryEndpoint = "<#YOUR_DISCOVERY_ENDPOINT#>" // e.g. "https://your-tenant.forgeblocks.com/am/oauth2//.well-known/openid-configuration"
+}
+
+@MainActor
+class AppJourney {
+ static let shared = AppJourney()
+
+ let journey: Journey
+
+ private init() {
+ journey = Journey.createJourney { journeyConfig in
+ journeyConfig.serverUrl = ServerConfig.serverUrl
+ journeyConfig.realm = ServerConfig.realm
+ journeyConfig.cookie = ServerConfig.cookieName
+ journeyConfig.logger = LogManager.standard
+ journeyConfig.module(PingJourney.OidcModule.config) { oidcConfig in
+ oidcConfig.clientId = ServerConfig.clientId
+ oidcConfig.scopes = ServerConfig.scopes
+ oidcConfig.redirectUri = ServerConfig.redirectUri
+ oidcConfig.discoveryEndpoint = ServerConfig.discoveryEndpoint
+ oidcConfig.logger = LogManager.standard
+ }
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/PasskeysSwiftUI.xctestplan b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/PasskeysSwiftUI.xctestplan
new file mode 100644
index 00000000..b6272f7a
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/PasskeysSwiftUI.xctestplan
@@ -0,0 +1,35 @@
+{
+ "configurations" : [
+ {
+ "id" : "24499A57-8A8C-49DD-9DF6-FD06943246D4",
+ "name" : "Test Scheme Action",
+ "options" : {
+
+ }
+ }
+ ],
+ "defaultOptions" : {
+ "targetForVariableExpansion" : {
+ "containerPath" : "container:PasskeysSwiftUI.xcodeproj",
+ "identifier" : "8B41F6442DEDD0D5001A66F9",
+ "name" : "PasskeysSwiftUI"
+ }
+ },
+ "testTargets" : [
+ {
+ "target" : {
+ "containerPath" : "container:PasskeysSwiftUIPackage",
+ "identifier" : "PasskeysSwiftUIFeatureTests",
+ "name" : "PasskeysSwiftUIFeatureTests"
+ }
+ },
+ {
+ "target" : {
+ "containerPath" : "container:PasskeysSwiftUI.xcodeproj",
+ "identifier" : "8B41F65B2DEDD0D6001A66F9",
+ "name" : "PasskeysSwiftUIUITests"
+ }
+ }
+ ],
+ "version" : 1
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/AuthenticatedViewModel.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/AuthenticatedViewModel.swift
new file mode 100644
index 00000000..230e2556
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/AuthenticatedViewModel.swift
@@ -0,0 +1,45 @@
+//
+// AuthenticatedViewModel.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import Observation
+import PingJourney
+import PingOidc
+
+@Observable
+@MainActor
+final class AuthenticatedViewModel {
+ var userInfo: [String: Any] = [:]
+ var isLoadingUserInfo = false
+ var userInfoError: String?
+ var isSignedOut = false
+
+ func loadUserInfo() async {
+ isLoadingUserInfo = true
+ userInfoError = nil
+ defer { isLoadingUserInfo = false }
+
+ guard let user = await AppJourney.shared.journey.journeyUser() else {
+ userInfoError = "No authenticated user found."
+ return
+ }
+
+ switch await user.userinfo(cache: false) {
+ case .success(let info):
+ userInfo = info
+ case .failure(let error):
+ userInfoError = error.localizedDescription
+ }
+ }
+
+ func signOut() async {
+ _ = await AppJourney.shared.journey.signOff()
+ isSignedOut = true
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/LoginViewModel.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/LoginViewModel.swift
new file mode 100644
index 00000000..7eccdb83
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/LoginViewModel.swift
@@ -0,0 +1,44 @@
+//
+// LoginViewModel.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import Foundation
+import Observation
+import PingJourney
+import PingOrchestrate
+
+@Observable
+@MainActor
+final class LoginViewModel {
+ var node: Node?
+ var isLoading = false
+
+ func start() async {
+ let journeyName = UserDefaults.standard.bool(forKey: UserDefaultsKey.biometricsEnabled)
+ ? JourneyName.fidoAuthentication
+ : JourneyName.login
+ await startJourney(name: journeyName)
+ }
+
+ func startJourney(name: String) async {
+ isLoading = true
+ defer { isLoading = false }
+ node = await AppJourney.shared.journey.start(name)
+ }
+
+ func next(continueNode: ContinueNode) async {
+ isLoading = true
+ defer { isLoading = false }
+ node = await continueNode.next()
+ }
+
+ func reset() {
+ node = nil
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/RegistrationViewModel.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/RegistrationViewModel.swift
new file mode 100644
index 00000000..317290b7
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/ViewModels/RegistrationViewModel.swift
@@ -0,0 +1,46 @@
+//
+// RegistrationViewModel.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import Foundation
+import Observation
+import PingJourney
+import PingOrchestrate
+
+@Observable
+@MainActor
+final class RegistrationViewModel {
+ var node: Node?
+ var isLoading = false
+ var isRegistered = false
+ var isFailure = false
+
+ func startRegistration() async {
+ isLoading = true
+ isRegistered = false
+ isFailure = false
+ defer { isLoading = false }
+ node = await AppJourney.shared.journey.start(JourneyName.fidoRegistration) { options in
+ options.forceAuth = true
+ }
+ }
+
+ func next(continueNode: ContinueNode) async {
+ isLoading = true
+ defer { isLoading = false }
+ let nextNode = await continueNode.next()
+ node = nextNode
+ if nextNode is SuccessNode {
+ UserDefaults.standard.set(true, forKey: UserDefaultsKey.biometricsEnabled)
+ isRegistered = true
+ } else if nextNode is FailureNode || nextNode is ErrorNode {
+ isFailure = true
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/AuthenticatedView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/AuthenticatedView.swift
new file mode 100644
index 00000000..6351c3f1
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/AuthenticatedView.swift
@@ -0,0 +1,182 @@
+//
+// AuthenticatedView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+import PingOrchestrate
+
+struct AuthenticatedView: View {
+ let successNode: SuccessNode
+ let onSignOut: () -> Void
+
+ @State private var viewModel = AuthenticatedViewModel()
+ @State private var showSettings = false
+
+ var body: some View {
+ NavigationStack {
+ ScrollView {
+ VStack(spacing: 0) {
+ AuthenticatedHeaderView()
+
+ VStack(spacing: 20) {
+ profileSection
+ signOutButton
+ }
+ .padding(24)
+ }
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar { settingsToolbarItem }
+ .sheet(isPresented: $showSettings) { SettingsView() }
+ .onChange(of: viewModel.isSignedOut) { _, signedOut in
+ if signedOut { onSignOut() }
+ }
+ .task {
+ await viewModel.loadUserInfo()
+ }
+ }
+ }
+
+ // MARK: - Profile Section
+
+ @ViewBuilder
+ private var profileSection: some View {
+ if viewModel.isLoadingUserInfo {
+ ProgressView("Loading profile…")
+ .frame(maxWidth: .infinity)
+ .padding(32)
+ } else if let errorMessage = viewModel.userInfoError {
+ Text(errorMessage)
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ .padding()
+ } else if !viewModel.userInfo.isEmpty {
+ UserInfoCard(userInfo: viewModel.userInfo)
+ }
+ }
+
+ private var signOutButton: some View {
+ Button("Sign Out") {
+ Task { await viewModel.signOut() }
+ }
+ .buttonStyle(PingDestructiveButtonStyle())
+ }
+
+ private var settingsToolbarItem: some ToolbarContent {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+ showSettings = true
+ } label: {
+ Image(systemName: "gear")
+ .foregroundColor(.pingRed)
+ }
+ }
+ }
+}
+
+// MARK: - Authenticated Header
+
+private struct AuthenticatedHeaderView: View {
+ var body: some View {
+ ZStack {
+ LinearGradient(
+ colors: [.pingRed, .pingRedDark],
+ startPoint: .topLeading,
+ endPoint: .bottomTrailing
+ )
+
+ VStack(spacing: 12) {
+ Image("Logo")
+ .resizable()
+ .scaledToFit()
+ .frame(width: 80, height: 80)
+
+ Image(systemName: "checkmark.shield.fill")
+ .font(.system(size: 36))
+ .foregroundColor(.white.opacity(0.9))
+
+ Text("Authenticated")
+ .font(.system(size: 22, weight: .bold))
+ .foregroundColor(.white)
+ }
+ .padding(.vertical, 32)
+ }
+ .ignoresSafeArea(edges: .top)
+ }
+}
+
+// MARK: - User Info Card
+
+private struct UserInfoCard: View {
+ let userInfo: [String: Any]
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 0) {
+ Text("Profile")
+ .font(.system(size: 13, weight: .semibold))
+ .foregroundColor(.secondary)
+ .textCase(.uppercase)
+ .padding(.bottom, 10)
+
+ VStack(spacing: 0) {
+ ForEach(userInfo.keys.sorted(), id: \.self) { key in
+ UserInfoRow(key: key, value: String(describing: userInfo[key] ?? ""))
+ }
+ }
+ .background(Color(.secondarySystemGroupedBackground))
+ .cornerRadius(12)
+ }
+ }
+}
+
+private struct UserInfoRow: View {
+ let key: String
+ let value: String
+
+ var body: some View {
+ HStack(alignment: .top, spacing: 12) {
+ Text(key)
+ .font(.caption)
+ .fontWeight(.medium)
+ .foregroundColor(.secondary)
+ .frame(width: 110, alignment: .leading)
+
+ Text(value)
+ .font(.caption)
+ .foregroundColor(.primary)
+ .multilineTextAlignment(.leading)
+
+ Spacer()
+ }
+ .padding(.horizontal, 16)
+ .padding(.vertical, 10)
+ Divider()
+ .padding(.leading, 16)
+ }
+}
+
+// MARK: - Destructive Button Style
+
+struct PingDestructiveButtonStyle: ButtonStyle {
+ func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .font(.headline)
+ .foregroundColor(.pingRed)
+ .padding()
+ .frame(maxWidth: .infinity)
+ .frame(height: 50)
+ .background(
+ RoundedRectangle(cornerRadius: 15)
+ .stroke(Color.pingRed, lineWidth: 1.5)
+ .opacity(configuration.isPressed ? 0.6 : 1)
+ )
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoAuthenticationCallbackView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoAuthenticationCallbackView.swift
new file mode 100644
index 00000000..1b78969f
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoAuthenticationCallbackView.swift
@@ -0,0 +1,75 @@
+//
+// FidoAuthenticationCallbackView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingFido
+
+struct FidoAuthenticationCallbackView: View {
+ let callback: FidoAuthenticationCallback
+ let onNext: () -> Void
+
+ @State private var errorMessage: String?
+ @State private var isAuthenticating = false
+
+ var body: some View {
+ VStack(spacing: 20) {
+ FidoIconView(systemName: "touchid", tint: .pingRed)
+
+ VStack(spacing: 6) {
+ Text("Biometric Authentication")
+ .font(.title2)
+ .fontWeight(.semibold)
+
+ Text("Use your passkey to sign in securely.")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ }
+
+ if let errorMessage {
+ ErrorMessageView(message: errorMessage)
+ }
+
+ Button(action: authenticate) {
+ if isAuthenticating {
+ ProgressView()
+ .tint(.white)
+ } else {
+ Text("Authenticate with Passkey")
+ }
+ }
+ .buttonStyle(PingPrimaryButtonStyle())
+ .disabled(isAuthenticating)
+ }
+ }
+
+ private func authenticate() {
+ Task {
+ isAuthenticating = true
+ defer { isAuthenticating = false }
+
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let window = windowScene.windows.first else {
+ errorMessage = "Unable to find active window."
+ return
+ }
+
+ let result = await callback.authenticate(window: window)
+
+ switch result {
+ case .success:
+ onNext()
+ case .failure(let error):
+ errorMessage = error.localizedDescription
+ onNext()
+ }
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoRegistrationCallbackView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoRegistrationCallbackView.swift
new file mode 100644
index 00000000..5e4d37d0
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/FidoRegistrationCallbackView.swift
@@ -0,0 +1,79 @@
+//
+// FidoRegistrationCallbackView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingFido
+
+struct FidoRegistrationCallbackView: View {
+ let callback: FidoRegistrationCallback
+ let onNext: () -> Void
+
+ @State private var deviceName = UIDevice.current.name
+ @State private var errorMessage: String?
+ @State private var isRegistering = false
+
+ var body: some View {
+ VStack(spacing: 20) {
+ FidoIconView(systemName: "faceid", tint: .pingRed)
+
+ VStack(spacing: 6) {
+ Text("Register Passkey")
+ .font(.title2)
+ .fontWeight(.semibold)
+
+ Text("Create a passkey for fast, secure biometric login.")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ }
+
+ PingTextField(placeholder: "Device Name", text: $deviceName)
+
+ if let errorMessage {
+ ErrorMessageView(message: errorMessage)
+ }
+
+ Button(action: register) {
+ if isRegistering {
+ ProgressView()
+ .tint(.white)
+ } else {
+ Text("Register with Passkey")
+ }
+ }
+ .buttonStyle(PingPrimaryButtonStyle())
+ .disabled(isRegistering)
+ }
+ }
+
+ private func register() {
+ Task {
+ isRegistering = true
+ defer { isRegistering = false }
+
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+ let window = windowScene.windows.first else {
+ errorMessage = "Unable to find active window."
+ return
+ }
+
+ let name = deviceName.isEmpty ? nil : deviceName
+ let result = await callback.register(deviceName: name, window: window)
+
+ switch result {
+ case .success:
+ onNext()
+ case .failure(let error):
+ errorMessage = error.localizedDescription
+ onNext()
+ }
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/NameCallbackView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/NameCallbackView.swift
new file mode 100644
index 00000000..a82007f1
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/NameCallbackView.swift
@@ -0,0 +1,26 @@
+//
+// NameCallbackView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+
+struct NameCallbackView: View {
+ let callback: NameCallback
+ @State private var username = ""
+
+ var body: some View {
+ PingTextField(
+ placeholder: callback.prompt.isEmpty ? "Username" : callback.prompt,
+ text: $username,
+ contentType: .username
+ )
+ .onChange(of: username) { callback.name = username }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/PasswordCallbackView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/PasswordCallbackView.swift
new file mode 100644
index 00000000..1665e041
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Callbacks/PasswordCallbackView.swift
@@ -0,0 +1,25 @@
+//
+// PasswordCallbackView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+
+struct PasswordCallbackView: View {
+ let callback: PasswordCallback
+ @State private var password = ""
+
+ var body: some View {
+ PingSecureField(
+ placeholder: callback.prompt.isEmpty ? "Password" : callback.prompt,
+ text: $password
+ )
+ .onChange(of: password) { callback.password = password }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Components/Theme.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Components/Theme.swift
new file mode 100644
index 00000000..2e76cb05
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/Components/Theme.swift
@@ -0,0 +1,189 @@
+//
+// Theme.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+
+extension Color {
+ static let pingRed = Color(red: 163.0 / 255.0, green: 19.0 / 255.0, blue: 0.0 / 255.0)
+ static let pingRedDark = Color(red: 0.6, green: 0.1, blue: 0.1)
+ static let pingTextField = Color(red: 220.0 / 255.0, green: 230.0 / 255.0, blue: 230.0 / 255.0)
+}
+
+// MARK: - Primary Action Button
+
+struct PingPrimaryButtonStyle: ButtonStyle {
+ func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .font(.headline)
+ .foregroundColor(.white)
+ .padding()
+ .frame(maxWidth: .infinity)
+ .frame(height: 50)
+ .background(Color.pingRed.opacity(configuration.isPressed ? 0.8 : 1))
+ .cornerRadius(15)
+ .shadow(color: .black.opacity(0.2), radius: 10, x: 0, y: 4)
+ }
+}
+
+// MARK: - Branded Header
+
+struct PingHeaderView: View {
+ var body: some View {
+ ZStack {
+ LinearGradient(
+ colors: [.pingRed, .pingRedDark],
+ startPoint: .topLeading,
+ endPoint: .bottomTrailing
+ )
+
+ VStack(spacing: 12) {
+ Image("Logo")
+ .resizable()
+ .scaledToFit()
+ .frame(width: 80, height: 80)
+
+ Text("Passkeys Demo")
+ .font(.system(size: 24, weight: .bold))
+ .foregroundColor(.white)
+
+ Text("Secure biometric authentication")
+ .font(.system(size: 14, weight: .medium))
+ .foregroundColor(.white.opacity(0.85))
+ }
+ .padding(.vertical, 32)
+ }
+ .ignoresSafeArea(edges: .top)
+ }
+}
+
+// MARK: - Branded Text Field
+
+struct PingTextField: View {
+ let placeholder: String
+ @Binding var text: String
+ var contentType: UITextContentType? = nil
+ var autocapitalization: TextInputAutocapitalization = .never
+
+ var body: some View {
+ TextField(placeholder, text: $text)
+ .textContentType(contentType)
+ .autocorrectionDisabled()
+ .textInputAutocapitalization(autocapitalization)
+ .padding(12)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(Color.pingTextField, lineWidth: 1.5)
+ )
+ }
+}
+
+// MARK: - Branded Secure Field
+
+struct PingSecureField: View {
+ let placeholder: String
+ @Binding var text: String
+ @State private var isSecure = true
+
+ var body: some View {
+ HStack {
+ Group {
+ if isSecure {
+ SecureField(placeholder, text: $text)
+ } else {
+ TextField(placeholder, text: $text)
+ }
+ }
+ .textContentType(.password)
+ .autocorrectionDisabled()
+ .textInputAutocapitalization(.never)
+
+ Button {
+ isSecure.toggle()
+ } label: {
+ Image(systemName: isSecure ? "eye.slash" : "eye")
+ .foregroundColor(.pingRed)
+ .frame(width: 20, height: 20)
+ }
+ }
+ .padding(12)
+ .background(
+ RoundedRectangle(cornerRadius: 8)
+ .stroke(Color.pingTextField, lineWidth: 1.5)
+ )
+ }
+}
+
+// MARK: - FIDO Icon View
+
+struct FidoIconView: View {
+ let systemName: String
+ let tint: Color
+
+ var body: some View {
+ ZStack {
+ Circle()
+ .fill(tint.opacity(0.12))
+ .frame(width: 88, height: 88)
+
+ Image(systemName: systemName)
+ .font(.system(size: 40))
+ .foregroundColor(tint)
+ }
+ }
+}
+
+// MARK: - Error Message View
+
+struct ErrorMessageView: View {
+ let message: String
+
+ var body: some View {
+ HStack(spacing: 6) {
+ Image(systemName: "exclamationmark.circle.fill")
+ .font(.caption)
+ Text(message)
+ .font(.caption)
+ .multilineTextAlignment(.leading)
+ }
+ .foregroundColor(.red)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.horizontal, 4)
+ }
+}
+
+// MARK: - Loading Overlay
+
+struct LoadingOverlay: View {
+ let message: String
+
+ init(_ message: String = "Loading…") {
+ self.message = message
+ }
+
+ var body: some View {
+ ZStack {
+ Color.black.opacity(0.35)
+ .ignoresSafeArea()
+
+ VStack(spacing: 12) {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .tint(.white)
+ .scaleEffect(1.4)
+
+ Text(message)
+ .font(.subheadline)
+ .foregroundColor(.white)
+ }
+ .padding(24)
+ .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContentView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContentView.swift
new file mode 100644
index 00000000..967410c8
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContentView.swift
@@ -0,0 +1,84 @@
+//
+// ContentView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingOrchestrate
+import PingJourney
+
+struct ContentView: View {
+ @State private var loginViewModel = LoginViewModel()
+
+ var body: some View {
+ Group {
+ 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() } }
+ )
+ }
+ }
+ .task {
+ await loginViewModel.start()
+ }
+ }
+
+ private func retry() {
+ loginViewModel.reset()
+ Task { await loginViewModel.start() }
+ }
+}
+
+// MARK: - Error View
+
+private struct ErrorView: View {
+ let message: String
+ let onRetry: () -> Void
+
+ var body: some View {
+ VStack(spacing: 24) {
+ Image(systemName: "exclamationmark.triangle.fill")
+ .font(.system(size: 56))
+ .foregroundColor(.pingRed)
+
+ VStack(spacing: 8) {
+ Text("Something went wrong")
+ .font(.title3)
+ .fontWeight(.semibold)
+
+ Text(message)
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ }
+
+ Button("Try Again", action: onRetry)
+ .buttonStyle(PingPrimaryButtonStyle())
+ .padding(.horizontal, 40)
+ }
+ .padding(32)
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContinueNodeView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContinueNodeView.swift
new file mode 100644
index 00000000..92c90d54
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/ContinueNodeView.swift
@@ -0,0 +1,61 @@
+//
+// ContinueNodeView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+import PingJourneyPlugin
+import PingOrchestrate
+import PingFido
+
+struct ContinueNodeView: View {
+ let node: ContinueNode
+ let onNext: () -> Void
+
+ var body: some View {
+ VStack(spacing: 16) {
+ ForEach(node.callbacks, id: \.id) { callback in
+ callbackView(for: callback)
+ }
+
+ if !hasSelfAdvancingCallback {
+ Button("Next", action: onNext)
+ .buttonStyle(PingPrimaryButtonStyle())
+ }
+ }
+ }
+
+ // FIDO callbacks advance the node themselves after the OS prompt completes,
+ // so we suppress the generic "Next" button when they are present.
+ private var hasSelfAdvancingCallback: Bool {
+ node.callbacks.contains { $0 is FidoRegistrationCallback || $0 is FidoAuthenticationCallback }
+ }
+
+ @ViewBuilder
+ private func callbackView(for callback: any Callback) -> some View {
+ switch callback {
+ case let nameCallback as NameCallback:
+ NameCallbackView(callback: nameCallback)
+ case let passwordCallback as PasswordCallback:
+ PasswordCallbackView(callback: passwordCallback)
+ case let textOutputCallback as TextOutputCallback:
+ Text(textOutputCallback.message)
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ .padding(.vertical, 4)
+ case let fidoRegistrationCallback as FidoRegistrationCallback:
+ FidoRegistrationCallbackView(callback: fidoRegistrationCallback, onNext: onNext)
+ case let fidoAuthenticationCallback as FidoAuthenticationCallback:
+ FidoAuthenticationCallbackView(callback: fidoAuthenticationCallback, onNext: onNext)
+ default:
+ EmptyView()
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/LoginView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/LoginView.swift
new file mode 100644
index 00000000..1a4c8bd5
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/LoginView.swift
@@ -0,0 +1,64 @@
+//
+// LoginView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+import PingOrchestrate
+
+struct LoginView: View {
+ let continueNode: ContinueNode?
+ let isLoading: Bool
+ let onNext: () -> Void
+
+ var body: some View {
+ ZStack {
+ ScrollView {
+ VStack(spacing: 0) {
+ PingHeaderView()
+
+ VStack(spacing: 24) {
+ if let continueNode {
+ ContinueNodeView(node: continueNode, onNext: onNext)
+ } else {
+ SignInPromptView(onSignIn: onNext)
+ }
+ }
+ .padding(24)
+ }
+ }
+
+ if isLoading {
+ LoadingOverlay("Signing in…")
+ }
+ }
+ }
+}
+
+// MARK: - Sign In Prompt
+
+private struct SignInPromptView: View {
+ let onSignIn: () -> Void
+
+ var body: some View {
+ VStack(spacing: 16) {
+ Text("Welcome back")
+ .font(.title2)
+ .fontWeight(.semibold)
+
+ Text("Sign in with your credentials or use a passkey for biometric authentication.")
+ .font(.subheadline)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+
+ Button("Sign In", action: onSignIn)
+ .buttonStyle(PingPrimaryButtonStyle())
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/SettingsView.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/SettingsView.swift
new file mode 100644
index 00000000..8a83526c
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI/Views/SettingsView.swift
@@ -0,0 +1,106 @@
+//
+// SettingsView.swift
+// PasskeysSwiftUI
+//
+// Copyright (c) 2026 Ping Identity Corporation. All rights reserved.
+//
+// This software may be modified and distributed under the terms
+// of the MIT license. See the LICENSE file for details.
+//
+
+import SwiftUI
+import PingJourney
+import PingOrchestrate
+
+struct SettingsView: View {
+ @Environment(\.dismiss) private var dismiss
+ @State private var registrationViewModel = RegistrationViewModel()
+ @State private var biometricsEnabled = UserDefaults.standard.bool(forKey: UserDefaultsKey.biometricsEnabled)
+
+ var body: some View {
+ NavigationStack {
+ ZStack {
+ settingsForm
+ if registrationViewModel.isLoading {
+ LoadingOverlay("Registering passkey…")
+ }
+ }
+ .navigationTitle("Settings")
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button("Done") { dismiss() }
+ .foregroundColor(.pingRed)
+ }
+ }
+ .onChange(of: registrationViewModel.isRegistered) { _, registered in
+ if registered { dismiss() }
+ }
+ .onChange(of: registrationViewModel.isFailure) { _, isFailure in
+ if isFailure { biometricsEnabled = false }
+ }
+ }
+ }
+
+ // MARK: - Settings Form
+
+ private var settingsForm: some View {
+ Form {
+ biometricsSection
+ registrationSection
+ errorSection
+ }
+ .tint(.pingRed)
+ }
+
+ private var biometricsSection: some View {
+ Section {
+ Toggle("Biometric / Passkey Login", isOn: $biometricsEnabled)
+ .onChange(of: biometricsEnabled) { _, isEnabled in
+ if isEnabled {
+ Task { await registrationViewModel.startRegistration() }
+ } else {
+ UserDefaults.standard.set(false, forKey: UserDefaultsKey.biometricsEnabled)
+ }
+ }
+ } header: {
+ Text("Authentication")
+ } footer: {
+ Text(biometricsEnabled
+ ? "Passkey login is enabled. You will be prompted with Face ID or Touch ID on the next sign-in."
+ : "Enable to register a passkey for biometric login.")
+ }
+ }
+
+ @ViewBuilder
+ private var registrationSection: some View {
+ if let continueNode = registrationViewModel.node as? ContinueNode {
+ Section("Passkey Registration") {
+ ContinueNodeView(node: continueNode, onNext: {
+ Task { await registrationViewModel.next(continueNode: continueNode) }
+ })
+ .padding(.vertical, 8)
+ }
+ }
+ }
+
+ @ViewBuilder
+ private var errorSection: some View {
+ if let failureNode = registrationViewModel.node as? FailureNode {
+ Section {
+ Label(failureNode.cause.localizedDescription, systemImage: "xmark.circle.fill")
+ .font(.subheadline)
+ .foregroundColor(.red)
+ }
+ } else if let errorNode = registrationViewModel.node as? ErrorNode {
+ Section {
+ Label(
+ errorNode.message.isEmpty ? "An error occurred during registration." : errorNode.message,
+ systemImage: "xmark.circle.fill"
+ )
+ .font(.subheadline)
+ .foregroundColor(.red)
+ }
+ }
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUIUITests/PasskeysSwiftUIUITests.swift b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUIUITests/PasskeysSwiftUIUITests.swift
new file mode 100644
index 00000000..d14ba0d9
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUIUITests/PasskeysSwiftUIUITests.swift
@@ -0,0 +1,26 @@
+import XCTest
+
+final class PasskeysSwiftUIUITests: XCTestCase {
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ @MainActor
+ func testExample() throws {
+ // UI tests must launch the application that they test.
+ let app = XCUIApplication()
+ app.launch()
+
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ XCTAssertTrue(true)
+ }
+}
diff --git a/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/README.md b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/README.md
new file mode 100644
index 00000000..89279f2a
--- /dev/null
+++ b/iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/README.md
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+# Passkeys Sample App using Swift/SwiftUI
+
+Ping provides these iOS samples to help demonstrate SDK functionality/implementation. They are provided "as is" and are not official products of Ping and are not officially supported.
+
+### Integrate with PingAM/AIC using Journeys and Passkeys:
+
+An example iOS project written in Swift/SwiftUI using the Ping iOS SDK, showcasing how to protect an application using Journeys and Passkeys. Based on the ["Set up passwordless authentication with Passkeys"](https://docs.pingidentity.com/sdks/latest/sdks/use-cases/how-to-go-passwordless-with-passkeys.html) blog post.
+
+## Requirements
+
+- Xcode: Latest version recommended
+- iOS 18.0 or higher
+- A physical iOS device or simulator
+- PingAM/AIC server configured with the following journeys:
+ - `Login` — standard username and password authentication
+ - `BlogWebAuthnRegistration` — passkey registration
+ - `BlogWebAuthnAuthentication` — passkey-based sign-in
+
+## Getting Started
+
+To try out this sample, perform the following steps:
+
+1. Configure Ping Services
+ Ensure you have a PingAM/AIC server set up with the required authentication journeys and an OAuth 2.0 application for native mobile apps. See the [documentation](https://docs.pingidentity.com/sdks/latest/sdks/serverconfiguration/pingone/create-oauth2-client.html) for details.
+
+2. Clone this repo:
+
+ ```
+ git clone https://github.com/ForgeRock/sdk-sample-apps.git
+ ```
+
+3. Open the workspace in Xcode:
+
+ ```
+ open iOS/swiftui-journey-module-passkeys/PasskeysSwiftUI/PasskeysSwiftUI.xcworkspace
+ ```
+
+4. Open `PasskeysSwiftUI/Config/AppConfiguration.swift` and update `ServerConfig` with your environment values:
+
+ ```swift
+ static let serverUrl = "https://your-server.example.com/am"
+ static let realm = "alpha"
+ static let cookieName = "your-cookie-name"
+ static let clientId = "your-client-id"
+ static let redirectUri = "yourapp://callback"
+ static let discoveryEndpoint = "https://your-server.example.com/am/oauth2/alpha/.well-known/openid-configuration"
+ ```
+
+5. Update the Associated Domains entitlement in `Config/PasskeysSwiftUI.entitlements` to match your server domain (required for the WebAuthn relying party ID):
+
+ ```xml
+ com.apple.developer.associated-domains
+
+ webcredentials:your-server.example.com
+ webcredentials:your-server.example.com?mode=develop
+
+ ```
+
+6. Build and run the app on a physical iOS device.
+
+## Features
+
+This sample demonstrates:
+
+- Journey-based authentication with callback handling (username/password)
+- FIDO2/WebAuthn passkey registration via `FidoRegistrationCallback`
+- FIDO2/WebAuthn passkey authentication via `FidoAuthenticationCallback`
+- Conditional login flow — automatically uses passkey sign-in when a passkey is registered
+- Session management and sign-out via the Journey SDK
+- OIDC user info display after successful authentication
+
+## Additional Resources
+
+- Ping SDK Documentation: https://docs.pingidentity.com/sdks/latest/sdks/index.html
+- Passwordless with Passkeys blog: https://docs.pingidentity.com/sdks/latest/sdks/use-cases/how-to-go-passwordless-with-passkeys.html