diff --git a/iOS/swiftui-davinci/Davinci.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iOS/swiftui-davinci/Davinci.xcworkspace/xcshareddata/swiftpm/Package.resolved index e7443da8..a83867bb 100644 --- a/iOS/swiftui-davinci/Davinci.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/iOS/swiftui-davinci/Davinci.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleSignIn-iOS.git", "state" : { - "revision" : "4dbde2358566466e21f05d69f9e770b0b81b6eb8", - "version" : "8.1.0-vwg-eap-1.0.0" + "revision" : "3859395ef0d8585208df69aaed8f116c693658ef", + "version" : "8.1.0-vwg-eap-1.1.0" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", - "version" : "8.0.2" + "revision" : "60da361632d0de02786f709bdc0c4df340f7613e", + "version" : "8.1.0" } }, { @@ -78,8 +78,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ForgeRock/ping-ios-sdk", "state" : { - "revision" : "f3f2836898c54efda9f5de4e3d1a418edfa7e8cb", - "version" : "1.2.0" + "branch" : "SDKS-4353_OIDC_Bug_fixes", + "revision" : "f5d58b8be5812f8beec32bac00e96307e02055c1" + } + }, + { + "identity" : "pingone-signals-sdk-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pingidentity/pingone-signals-sdk-ios.git", + "state" : { + "revision" : "bf37bd85fa909428d764630c71cf0d4f3d2d7e05", + "version" : "5.3.0" } }, { diff --git a/iOS/swiftui-davinci/Davinci/Davinci.xcodeproj/project.pbxproj b/iOS/swiftui-davinci/Davinci/Davinci.xcodeproj/project.pbxproj index 9451288b..f8c66b84 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci.xcodeproj/project.pbxproj +++ b/iOS/swiftui-davinci/Davinci/Davinci.xcodeproj/project.pbxproj @@ -55,6 +55,8 @@ A5EED25C2CF7B5480064B168 /* UserInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25B2CF7B5480064B168 /* UserInfoView.swift */; }; A5EED25E2CF7B8F80064B168 /* UserInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */; }; A5EED2602CF7B9380064B168 /* LogOutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */; }; + B1OIDC012024ABCD0001 /* OidcLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1OIDC022024ABCD0002 /* OidcLoginView.swift */; }; + B1OIDC032024ABCD0003 /* OidcLoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */; }; EC83DE312E5F2BD600D2B5B4 /* DeviceRegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE2F2E5F2BD600D2B5B4 /* DeviceRegistrationView.swift */; }; EC83DE322E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE2E2E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift */; }; EC83DE332E5F2BD600D2B5B4 /* PhoneNumberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE302E5F2BD600D2B5B4 /* PhoneNumberView.swift */; }; @@ -66,6 +68,7 @@ EC83DE5A2E5F2D1D00D2B5B4 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = EC83DE592E5F2D1D00D2B5B4 /* SVGKit */; }; EC83DE5C2E5F2D1D00D2B5B4 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = EC83DE5B2E5F2D1D00D2B5B4 /* SVGKitSwift */; }; EC83DE5E2E5F2EFE00D2B5B4 /* AsyncSVGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC83DE5D2E5F2EFE00D2B5B4 /* AsyncSVGImage.swift */; }; + ECCC647B2E783FCB00CD9650 /* PingJourney in Frameworks */ = {isa = PBXBuildFile; productRef = ECCC647A2E783FCB00CD9650 /* PingJourney */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -110,6 +113,8 @@ A5EED25B2CF7B5480064B168 /* UserInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoView.swift; sourceTree = ""; }; A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoViewModel.swift; sourceTree = ""; }; A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogOutViewModel.swift; sourceTree = ""; }; + B1OIDC022024ABCD0002 /* OidcLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OidcLoginView.swift; sourceTree = ""; }; + B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OidcLoginViewModel.swift; sourceTree = ""; }; EC83DE2E2E5F2BD600D2B5B4 /* DeviceAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAuthenticationView.swift; sourceTree = ""; }; EC83DE2F2E5F2BD600D2B5B4 /* DeviceRegistrationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRegistrationView.swift; sourceTree = ""; }; EC83DE302E5F2BD600D2B5B4 /* PhoneNumberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberView.swift; sourceTree = ""; }; @@ -133,6 +138,7 @@ A52877782D9C71C400B4885D /* PingBrowser in Frameworks */, A51C18EC2CF5247200A78241 /* PingOidc in Frameworks */, EC83DE5C2E5F2D1D00D2B5B4 /* SVGKitSwift in Frameworks */, + ECCC647B2E783FCB00CD9650 /* PingJourney in Frameworks */, A51313D32D9C6C640086F846 /* PingLogger in Frameworks */, A52877822D9C71C400B4885D /* PingOrchestrate in Frameworks */, EC83DE372E5F2CA200D2B5B4 /* PingExternalIdPApple in Frameworks */, @@ -193,6 +199,8 @@ A5EED25F2CF7B9380064B168 /* LogOutViewModel.swift */, A5EED25B2CF7B5480064B168 /* UserInfoView.swift */, A5EED25D2CF7B8F80064B168 /* UserInfoViewModel.swift */, + B1OIDC022024ABCD0002 /* OidcLoginView.swift */, + B1OIDC042024ABCD0004 /* OidcLoginViewModel.swift */, 3A5441342BCDF10B00385131 /* Assets.xcassets */, 3A5441362BCDF10B00385131 /* Preview Content */, ); @@ -339,6 +347,8 @@ A5A5F8612D5CE16A0053BF24 /* TextView.swift in Sources */, 3A3709962BEB038200AA7B51 /* AccessTokenView.swift in Sources */, A5A5F8652D5CE4A60053BF24 /* ErrorView.swift in Sources */, + B1OIDC012024ABCD0001 /* OidcLoginView.swift in Sources */, + B1OIDC032024ABCD0003 /* OidcLoginViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -584,8 +594,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ForgeRock/ping-ios-sdk"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.2.0; + branch = "SDKS-4353_OIDC_Bug_fixes"; + kind = branch; }; }; EC83DE582E5F2D1D00D2B5B4 /* XCRemoteSwiftPackageReference "SVGKit" */ = { @@ -744,6 +754,11 @@ package = EC83DE582E5F2D1D00D2B5B4 /* XCRemoteSwiftPackageReference "SVGKit" */; productName = SVGKitSwift; }; + ECCC647A2E783FCB00CD9650 /* PingJourney */ = { + isa = XCSwiftPackageProductDependency; + package = A57584782D9DD9EC00AFD754 /* XCRemoteSwiftPackageReference "ping-ios-sdk" */; + productName = PingJourney; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 3A5441252BCDF10900385131 /* Project object */; diff --git a/iOS/swiftui-davinci/Davinci/Davinci/AccessTokenViewModel.swift b/iOS/swiftui-davinci/Davinci/Davinci/AccessTokenViewModel.swift index 019b1997..448c7c80 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/AccessTokenViewModel.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/AccessTokenViewModel.swift @@ -11,6 +11,8 @@ import Foundation import PingLogger +import PingOrchestrate +import PingOidc /// A view model responsible for managing the access token state. /// - This class handles fetching the access token using the DaVinci SDK and logs the results. @@ -29,19 +31,29 @@ class AccessTokenViewModel: ObservableObject { } } - /// Fetches the access token using the DaVinci SDK. - /// - The method checks for a successful token retrieval and updates the `accessToken` property. + /// Fetches the access token using either the DaVinci SDK or OIDC Web SDK. + /// - The method checks both daVinci and oidcLogin for an authenticated user and retrieves their token. /// - Logs the success or failure result using `PingLogger`. func accessToken() async { - /// Request the token from the DaVinci SDK - let token = await davinci.user()?.token() + var tokenResult: Result? + + /// Check DaVinci user first + if let daVinciUser = await daVinci.daVinciUser() { + tokenResult = await daVinciUser.token() + LogManager.standard.i("Fetching token from DaVinci user") + } + /// If no DaVinci user, check OIDC user + else if let oidcUser = await oidcLogin.user() { + tokenResult = await oidcUser.token() + LogManager.standard.i("Fetching token from OIDC user") + } /// Process the token result - switch token { - case .success(let accessToken): + switch tokenResult { + case .success(let token): /// Update the UI on the main thread with the received token await MainActor.run { - self.accessToken = String(describing: accessToken) + self.accessToken = String(describing: token.accessToken) } /// Log the successful token retrieval LogManager.standard.i("AccessToken: \(self.accessToken)") @@ -53,8 +65,11 @@ class AccessTokenViewModel: ObservableObject { /// Log the error that occurred LogManager.standard.e("", error: error) case .none: - /// No response received, no further action required - break + /// No authenticated user found in either DaVinci or OIDC + await MainActor.run { + self.accessToken = "No authenticated user found" + } + LogManager.standard.i("No authenticated user found in either DaVinci or OIDC") } } } diff --git a/iOS/swiftui-davinci/Davinci/Davinci/ContentView.swift b/iOS/swiftui-davinci/Davinci/Davinci/ContentView.swift index e3b259cd..e9d98fb0 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/ContentView.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/ContentView.swift @@ -42,6 +42,11 @@ struct ContentView: View { NavigationLink(value: "DaVinci") { Text("Launch DaVinci") } + /// Navigation option to launch OIDC login. + /// - Takes the user to the OIDC Web authentication flow. + NavigationLink(value: "OIDC") { + Text("Launch OIDC Login") + } /// Navigation option to access the token-related view. /// - Shows the user's current access token. NavigationLink(value: "Token") { @@ -62,6 +67,8 @@ struct ContentView: View { switch item { case "DaVinci": DavinciView(path: $path) + case "OIDC": + OidcLoginView(path: $path) case "Token": AccessTokenView() case "User": diff --git a/iOS/swiftui-davinci/Davinci/Davinci/DavinciViewModel.swift b/iOS/swiftui-davinci/Davinci/Davinci/DavinciViewModel.swift index 39fdae3f..239906a6 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/DavinciViewModel.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/DavinciViewModel.swift @@ -22,13 +22,13 @@ import PingExternalIdP /// - Redirect URI /// - Discovery Endpoint /// - Other optional fields -public let davinci = DaVinci.createDaVinci { config in - //TODO: Provide here the Server configuration. Add the PingOne server Discovery Endpoint and the OAuth2.0 client details - config.module(OidcModule.config) { oidcValue in - oidcValue.clientId = <#"Client ID"#> - oidcValue.scopes = [<#"scope1"#>, <#"scope2"#>, <#"scope3"#>] - oidcValue.redirectUri = <#"Redirect URI"#> - oidcValue.discoveryEndpoint = <#"Discovery Endpoint"#> +public let daVinci = DaVinci.createDaVinci { config in + config.module(PingDavinci.OidcModule.config) { oidcValue in + oidcValue.clientId = "<#CLIENT_ID#>" + oidcValue.scopes = Set(["openid", "email", "address", "phone", "profile"]) + oidcValue.redirectUri = "<#REDIRECT_URI#>" + oidcValue.acrValues = "<#ACR_VALUES#>" + oidcValue.discoveryEndpoint = "<#DISCOVERY_ENDPOINT#>" } } @@ -60,7 +60,7 @@ class DavinciViewModel: ObservableObject { } // Starts the DaVinci orchestration process and retrieves the first node. - let next = await davinci.start() + let next = await daVinci.start() await MainActor.run { self.state = DavinciState(previous: next , node: next) diff --git a/iOS/swiftui-davinci/Davinci/Davinci/LogOutView.swift b/iOS/swiftui-davinci/Davinci/Davinci/LogOutView.swift index bb1d4aeb..13ee993f 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/LogOutView.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/LogOutView.swift @@ -23,8 +23,9 @@ struct LogOutView: View { NextButton(title: "Proceed to logout") { Task { await logoutViewModel.logout() - path.removeLast() - path.append("DaVinci") + if path.count > 0 { + path.removeLast() + } } } .navigationTitle("Logout") diff --git a/iOS/swiftui-davinci/Davinci/Davinci/LogOutViewModel.swift b/iOS/swiftui-davinci/Davinci/Davinci/LogOutViewModel.swift index 0b0c00a9..c92f5682 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/LogOutViewModel.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/LogOutViewModel.swift @@ -18,15 +18,30 @@ class LogOutViewModel: ObservableObject { /// - Updated with a completion message when logout is successful. @Published var logout: String = "" - /// Performs the user logout process using the DaVinci SDK. - /// - Executes the `logout()` method from the DaVinci user object asynchronously. + /// Performs the user logout process using either the DaVinci SDK or OIDC Web SDK. + /// - Executes the appropriate `logout()` method based on which user is authenticated. /// - Updates the `logout` property with a completion message upon success. func logout() async { - /// Call the DaVinci SDK's logout method - await davinci.user()?.logout() + var logoutMessage = "" + + /// Check for DaVinci user first + if let daVinciUser = await daVinci.daVinciUser() { + await daVinciUser.logout() + logoutMessage = "DaVinci user logout completed" + } + /// Check for OIDC user + if let oidcUser = await oidcLogin.oidcLoginUser() { + await oidcUser.logout() + logoutMessage = "OIDC user logout completed" + } + /// If no authenticated user found + else { + logoutMessage = "No authenticated user found" + } + /// Update the UI on the main thread await MainActor.run { - logout = "Logout completed" + logout = logoutMessage } } } diff --git a/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginView.swift b/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginView.swift new file mode 100644 index 00000000..41b2613b --- /dev/null +++ b/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginView.swift @@ -0,0 +1,82 @@ +// +// OidcLoginView.swift +// Davinci +// +// Copyright (c) 2024 - 2025 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 SwiftUI +import PingOidc + +/// The main view for OIDC login authentication. +struct OidcLoginView: View { + /// The view model that manages the OIDC login flow logic. + @StateObject private var oidcLoginViewModel = OidcLoginViewModel() + /// A binding to the navigation stack path. + @Binding var path: [String] + + var body: some View { + ZStack { + ScrollView { + VStack { + Spacer() + // Handle different types of authentication states. + switch oidcLoginViewModel.state { + case .success( _ ): + VStack{}.onAppear { + path.removeLast() + path.append("Token") + } + case .failure(let error): + ErrorView(message: error.localizedDescription) + case .none: + VStack(spacing: 20) { + /// App logo displayed at the top of the view + Image("Logo").resizable().scaledToFill().frame(width: 100, height: 100) + + Text("OIDC Login") + .font(.title) + .fontWeight(.bold) + + Text("Authenticate using OIDC Web flow") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + + Button("Start OIDC Login") { + Task { + await oidcLoginViewModel.startOidcLogin() + } + } + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(10) + } + .padding() + } + } + } + + Spacer() + + // Show an activity indicator when loading. + if oidcLoginViewModel.isLoading { + /// Semi-transparent overlay to indicate loading state + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + + /// Circular progress indicator that provides visual feedback during loading operations + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(2) + .tint(.blue) + } + } + .navigationTitle("OIDC Login") + } +} diff --git a/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginViewModel.swift b/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginViewModel.swift new file mode 100644 index 00000000..46aa6ea1 --- /dev/null +++ b/iOS/swiftui-davinci/Davinci/Davinci/OidcLoginViewModel.swift @@ -0,0 +1,69 @@ +// +// OidcLoginViewModel.swift +// Davinci +// +// Copyright (c) 2024 - 2025 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 PingOrchestrate +import PingOidc + +/// Creates an OIDC login instance with configuration. +public let oidcLogin = OidcWeb.createOidcWeb { config in + config.module(PingOidc.OidcModule.config) { oidcValue in + oidcValue.clientId = "<#CLIENT_ID#>" + oidcValue.scopes = Set(["openid", "email", "address", "phone", "profile"]) + oidcValue.redirectUri = "<#REDIRECT_URI#>" + oidcValue.acrValues = "<#ACR_VALUES#>" + oidcValue.discoveryEndpoint = "<#DISCOVERY_ENDPOINT#>" + } +} + +/// A view model that manages the flow and state of the OIDC login process. +/// - Responsible for: +/// - Starting the OIDC login flow +/// - Managing the authentication state +/// - Handling success and failure cases +@MainActor +class OidcLoginViewModel: ObservableObject { + /// Published property that holds the current authentication state. + @Published public var state: Result? + /// Published property to track whether the view is currently loading. + @Published public var isLoading: Bool = false + + /// Starts the OIDC login process. + /// - Sets the loading state and initiates the authentication flow. + public func startOidcLogin() async { + await MainActor.run { + isLoading = true + } + + do { + // Start the OIDC login process + let result = try await oidcLogin.authorize { options in + // Additional parameters can be set here if needed + options.additionalParameters = [:] + } + + await MainActor.run { + self.state = result + self.isLoading = false + } + } catch { + await MainActor.run { + self.state = .failure(OidcError.unknown(message: error.localizedDescription)) + self.isLoading = false + } + } + } + + /// Gets the current OIDC user if available. + /// - Returns: The current authenticated user or nil. + public func getOidcUser() async -> User? { + return await oidcLogin.user() + } +} diff --git a/iOS/swiftui-davinci/Davinci/Davinci/UserInfoViewModel.swift b/iOS/swiftui-davinci/Davinci/Davinci/UserInfoViewModel.swift index 4e80c74e..3dfda172 100644 --- a/iOS/swiftui-davinci/Davinci/Davinci/UserInfoViewModel.swift +++ b/iOS/swiftui-davinci/Davinci/Davinci/UserInfoViewModel.swift @@ -11,6 +11,8 @@ import SwiftUI import PingLogger +import PingOrchestrate +import PingOidc /// A view model responsible for fetching and managing user information. /// - Provides a published `userInfo` property that is updated with user information or error messages. @@ -29,13 +31,25 @@ class UserInfoViewModel: ObservableObject { } } - /// Fetches user information from the DaVinci SDK. + /// Fetches user information from either the DaVinci SDK or OIDC Web SDK. /// - The method retrieves user details as a dictionary and formats them as a string for display. /// - Updates the `userInfo` property with the fetched data or an error message. /// - Logs success and error messages using `PingLogger`. func fetchUserInfo() async { - let userInfo = await davinci.user()?.userinfo(cache: false) - switch userInfo { + var userInfoResult: Result? + + /// Check DaVinci user first + if let daVinciUser = await daVinci.daVinciUser() { + userInfoResult = await daVinciUser.userinfo(cache: false) + LogManager.standard.i("Fetching user info from DaVinci user") + } + /// If no DaVinci user, check OIDC user + else if let oidcUser = await oidcLogin.user() { + userInfoResult = await oidcUser.userinfo(cache: false) + LogManager.standard.i("Fetching user info from OIDC user") + } + + switch userInfoResult { case .success(let userInfoDictionary): // On success, format the dictionary into a string and update `userInfo`. await MainActor.run { @@ -43,16 +57,18 @@ class UserInfoViewModel: ObservableObject { userInfoDictionary.forEach { userInfoDescription += "\($0): \($1)\n" } self.userInfo = userInfoDescription } - LogManager.standard.i("UserInfo: \(String(describing: self.userInfo))") + LogManager.standard.i("User info retrieved successfully") case .failure(let error): - // On failure, update `userInfo` with an error message and log the error. + // On failure, update `userInfo` with an error message. await MainActor.run { - self.userInfo = "Error: \(error.localizedDescription)" + self.userInfo = "Failed to get user info: \(error.localizedDescription)" } - LogManager.standard.e("", error: error) + LogManager.standard.e("Failed to get user info: \(error)", error: error) case .none: - // No data received, no further action required. - break + await MainActor.run { + self.userInfo = "No authenticated user found" + } + LogManager.standard.w("No authenticated user found", error: nil) } } } diff --git a/iOS/swiftui-davinci/README.md b/iOS/swiftui-davinci/README.md index 266e355f..303bef50 100644 --- a/iOS/swiftui-davinci/README.md +++ b/iOS/swiftui-davinci/README.md @@ -9,31 +9,109 @@ 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 PingOne DaVinci: +### Dual Authentication Options: -- An example iOS project written in Swift/SwiftUI making use of the SDK. The sample supports the OOTB DaVinci Login flow. +This sample app demonstrates two authentication approaches: + +1. **PingOne DaVinci Flow**: Complete orchestrated authentication journey with custom flows +2. **OIDC Direct Login**: Standard OIDC/OAuth 2.0 authentication flow + +- An example iOS project written in Swift/SwiftUI making use of the Ping SDK. The sample supports both DaVinci orchestrated flows and direct OIDC authentication. ## Requirements - Xcode: Latest version recommended -- PingOne DaVinci +- PingOne DaVinci (for DaVinci flows) +- PingOne OIDC Application (for OIDC flows) - iOS 16.6 or higher +## Authentication Flows + +### DaVinci Flow +The DaVinci flow provides orchestrated authentication with custom journeys, multi-factor authentication, and complex business logic. This flow is ideal for: +- Custom authentication experiences +- Multi-step authentication processes +- Integration with external identity providers +- Complex policy enforcement + +### OIDC Login Flow +The OIDC login flow provides standard OAuth 2.0/OIDC authentication. This flow is ideal for: +- Simple username/password authentication +- Standard OAuth 2.0 compliance +- Quick integration with existing OIDC providers +- Minimal setup requirements + ## Getting Started -To try out the DaVinci iOS SDK sample, perform these steps: -1. Configure Ping Services - Ensure that you registered an OAuth 2.0 application for native mobile apps in PingOne. More details in this [documentation](https://backstage.forgerock.com/docs/sdks/latest/sdks/serverconfiguration/pingone/create-oauth2-client.html). +To try out the iOS SDK sample with dual authentication support, perform these steps: + +### 1. Configure Ping Services +Ensure that you registered an OAuth 2.0 application for native mobile apps in PingOne. More details in this [documentation](https://backstage.forgerock.com/docs/sdks/latest/sdks/serverconfiguration/pingone/create-oauth2-client.html). + +### 2. Clone this repo: + +``` +git clone https://github.com/ForgeRock/sdk-sample-apps.git +``` + +### 3. Open the iOS sample project +Open the iOS sample project (swiftui-davinci) in [Xcode](https://developer.apple.com/xcode/). + +### 4. Configure OAuth 2.0 Settings + +#### For DaVinci Flow: +Update the configuration in `DavinciViewModel.swift`: +```swift +public let daVinci = DaVinci.createDaVinci { config in + config.module(PingDavinci.OidcModule.config) { oidcValue in + oidcValue.clientId = "<#CLIENT_ID#>" + oidcValue.scopes = Set(["openid", "email", "address", "phone", "profile"]) + oidcValue.redirectUri = "<#REDIRECT_URI#>" + oidcValue.acrValues = "<#ACR_VALUES#>" + oidcValue.discoveryEndpoint = "<#DISCOVERY_ENDPOINT#>" + } +} +``` + +#### For OIDC Login Flow: +Update the configuration in `OidcLoginViewModel.swift`: +```swift +public let oidcLogin = OidcWeb.createOidcWeb { config in + config.module(PingOidc.OidcModule.config) { oidcValue in + oidcValue.clientId = "<#CLIENT_ID#>" + oidcValue.scopes = Set(["openid", "email", "address", "phone", "profile"]) + oidcValue.redirectUri = "<#REDIRECT_URI#>" + oidcValue.acrValues = "<#ACR_VALUES#>" + oidcValue.discoveryEndpoint = "<#DISCOVERY_ENDPOINT#>" + } +} +``` + +### 5. Replace Placeholder Values +Replace the placeholder values with your registered OAuth 2.0 application details: +- `<#CLIENT_ID#>`: Your PingOne application client ID +- `<#REDIRECT_URI#>`: Your configured redirect URI (e.g., `com.yourcompany.yourapp://oauth2redirect`) +- `<#ACR_VALUES#>`: Your DaVinci flow policy ID (for DaVinci flow) +- `<#DISCOVERY_ENDPOINT#>`: Your PingOne discovery endpoint URL + +### 6. Launch the Application +Launch the app on an iOS Device or a Simulator and choose your authentication method: +- **"Launch DaVinci"**: Experience the full DaVinci orchestrated flow +- **"Launch OIDC Login"**: Use direct OIDC authentication + +## Features -2. Clone this repo: +### Dual Authentication Support +- **Unified User Management**: Both flows integrate seamlessly, allowing users to authenticate via either method +- **Token Management**: Access tokens and user information from either authentication source +- **Consistent Logout**: Proper logout handling for both authentication types - ``` - git clone https://github.com/ForgeRock/sdk-sample-apps.git - ``` -3. Open the iOS sample project(swiftui-davinci) in [Xcode](https://developer.apple.com/xcode/). -4. Open the `DavinciViewModel.swift` file within the project. -5. Locate the TODO and replace the placeholder strings in the Oidc module configuration with the values of your registered OAuth 2.0 application. -6. Launch the app on an iOS Device or a Simulator. +### ViewModels Included +- `DavinciViewModel.swift`: Manages DaVinci orchestrated flows +- `OidcLoginViewModel.swift`: Handles direct OIDC authentication +- `AccessTokenViewModel.swift`: Retrieves access tokens from either authentication source +- `UserInfoViewModel.swift`: Fetches user information from either authentication source +- `LogOutViewModel.swift`: Handles logout for both authentication types ## Additional Resources Ping SDK Documentation: https://docs.pingidentity.com/sdks/latest/sdks/index.html