Quickstart — iOS#

This walkthrough gets a HAAPI-authenticated login working in a fresh iOS app in roughly 15 minutes. It uses the UI Layer (IdsvrHaapiUIKit) because prebuilt screens are the fastest path to a working flow. If your app needs custom screens, start at the layer chooser instead.

Before You Start#

You’ll need:

  • Xcode 27+ and an iOS 15+ deployment target (required for HAAPI iOS frameworks starting from 5.7.0).
  • A Curity Identity Server with a HAAPI-capable OAuth client configured. The clientId, base URL, token endpoint, authorization endpoint, and redirect URI for that client.
  • An iOS device or simulator. If attestation isn’t available on the target device, configure DCR fallback (covered in step 6).

Step 1 — Install the Framework#

The iOS UIKit ships as its own artifact, IdsvrHaapiUIKit. The Driver and SDK are pulled in as transitive dependencies — no separate declarations needed.

dependencies: [
    .package(url: "https://github.com/curityio/ios-idsvr-haapi-sdk-dist")
]
// ... and in your target's dependencies:
.product(name: "IdsvrHaapiUIKit", package: "ios-idsvr-haapi-sdk-dist")
pod 'IdsvrHaapiUIKit'

CocoaPods on Xcode 27 with release 5.6.0 or earlier. Those podspecs declare an iOS 14.0 deployment target, which Xcode 27 rejects outright:

Pods.xcodeproj: error: The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set
to 14.0, but the range of supported deployment target versions is 15.0 to 27.0.x.

Raise the generated pod targets in your Podfile until you move to 5.7.0, whose podspecs declare 15.0 and make this unnecessary:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      current = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']
      if current.nil? || Gem::Version.new(current.to_s) < Gem::Version.new('15.0')
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
      end
    end
  end
end

It only ever raises a target, so a pod declaring a higher minimum keeps it.

In your Swift files, import IdsvrHaapiUIKit.

Step 2 — Build the Configuration#

Declare the configuration as an AppDelegate property (so it’s built once at instantiation) and build the HaapiUIKitApplication in application(_:didFinishLaunchingWithOptions:):

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var haapiUIKitConfiguration = HaapiUIKitConfigurationBuilder(
        clientId: "my-haapi-client",
        baseUrl: URL(string: "https://idsvr.example.com")!,
        tokenEndpointUrl: URL(string: "https://idsvr.example.com/oauth/v2/oauth-token")!,
        authorizationEndpointUrl: URL(string: "https://idsvr.example.com/oauth/v2/oauth-authorize")!,
        appRedirect: "app://haapi"
    ).build()

    var haapiUIKitApplication: HaapiUIKitApplication!

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        haapiUIKitApplication = HaapiUIKitApplicationBuilder(
            haapiUIKitConfiguration: haapiUIKitConfiguration
        ).build()
        return true
    }
}

This is the same pattern documented in Configuration — your final code should match.

Step 3 — Conform to HaapiFlowResult#

Pick the view controller that will trigger authentication and make it conform to HaapiFlowResult:

class LoginViewController: UIViewController, HaapiFlowResult {
    func didReceiveOAuthTokenModel(_ tokenModel: OAuthTokenModel) {
        // Persist tokens (Keychain) and navigate to authenticated state.
        // REMOVE THIS print BEFORE SHIPPING — it leaks the access token to logs.
        print("Got token: \(tokenModel.accessToken)")
    }

    func didReceiveError(_ error: Error) {
        // Show user-facing error
        print("Flow error: \(error)")
    }
}

Step 4 — Start the Flow#

Trigger the flow on user action — typically a “Sign In” button:

@IBAction func signInTapped(_ sender: UIButton) {
    do {
        try HaapiFlow.start(
            from: self,
            haapiUIKitApplication: UIApplication.shared.haapiUIKitApplication,
            haapiDeepLinkManageable: HaapiDeepLinkManager.shared
        )
    } catch {
        // Configuration error — flow couldn't start
    }
}

extension UIApplication {
    var haapiUIKitApplication: HaapiUIKitApplication {
        guard let appDelegate = delegate as? AppDelegate else {
            fatalError("AppDelegate not configured")
        }
        return appDelegate.haapiUIKitApplication
    }
}

In Info.plist, register your redirect URI scheme (app in the example above) under URL Types. Then in AppDelegate:

func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
    return HaapiDeepLinkManager.shared.handle(url: url)
}

This lets the framework receive the redirect after an external-browser leg of the flow.

Step 6 — DCR Fallback for Untrusted Devices (Optional)#

When the device doesn’t support hardware-backed key attestation, or when the server rejects the attestation result (older OS versions, certain device states, distribution-channel mismatches), configure DCR fallback in step 2:

let config = HaapiUIKitConfigurationBuilder(
    clientId: "my-haapi-client",
    baseUrl: URL(string: "https://idsvr.example.com")!,
    tokenEndpointUrl: URL(string: "https://idsvr.example.com/oauth/v2/oauth-token")!,
    authorizationEndpointUrl: URL(string: "https://idsvr.example.com/oauth/v2/oauth-authorize")!,
    appRedirect: "app://haapi"
)
.setClientAuthenticationMethod(method: ClientAuthenticationMethodSecret("dev-secret"))
.setDCRConfiguration(configuration: DCRConfiguration(
    templateClientId: "dcr-template-client-haapi-ios",
    clientRegistrationEndpointUrl: URL(string: "https://idsvr.example.com/oauth/oauth-registration")!
))
.build()

The server must have a matching template client configured — see DCR for the server-side prerequisites.

Run It#

Build and run. Tap your sign-in button; the framework presents the login screen, walks the user through the authentication flow, and delivers the access token to didReceiveOAuthTokenModel(_:).

Next: Theming to brand the prebuilt screens · Presentation Options to tune modal vs stack and polling cadence · iOS UIKit for the full topic list

Was this helpful?