iOS Representations#

The Curity HAAPI iOS SDK delivers every HAAPI response as a typed Swift value. HaapiResult is the top-level enum returned by HaapiManager.start, submitForm, and followLink; its .representation case carries a HaapiRepresentation whose concrete type tells you which step the server is asking the client to render.

The same model hierarchy exists on Android and React Native — see Android Representations and React Native Representations for the per-platform shape. This page is the iOS narrative reference.

SDK-thrown errors are inside HaapiResult. Network failures, attestation failures, and other framework errors arrive as the third enum case, .error(Error). Match on it the same way you match on .representation and .problem. See Error Handling for the typed HaapiError hierarchy.

Response Hierarchy#

HaapiResult is a Swift enum with three cases. Every call to HaapiManager.start, submitForm, or followLink resolves to one of them:

CaseWhen it surfacesCarries
.representation(HaapiRepresentation)The flow continues with another stepA concrete struct conforming to HaapiRepresentation (or ClientOperationStep)
.problem(ProblemRepresentation)The server rejected the request (RFC 7807)A type conforming to Problem — InvalidInputProblem, AuthorizationProblem, or the base Problem
.error(Error)Framework / transport failureAn Error value — cast to HaapiError for the typed hierarchy

Match on the enum case first, then narrow further by casting the associated value to the concrete step or problem type.

Step Types#

HaapiRepresentation is a Swift protocol implemented by 14 concrete structs. Two distinct protocols capture the model — HaapiRepresentation for renderable steps and ClientOperationStep for steps that delegate to an external operation.

Common fields#

Every step exposes these base fields through the protocol:

FieldTypeDescription
typeRepresentationTypeHAAPI representation type (8 values)
metadataMetadata?Template area and view name
actions[Action]Available actions for this step
links[Link]Navigation links (e.g., “forgot password”)
messages[UserMessage]User-facing messages to display
propertiesProperties?Step-specific properties

The 14 step types#

Grouped by purpose:

GroupConcrete typeExtra fields
InteractiveAuthenticatorSelectorSteptitle: Message, authenticators: [AuthenticatorOption]
InteractiveFormStep(form actions inside actions)
UserConsentStep—
Flow controlRedirectionStepredirectAction: Action
PollingStepmainAction, cancelAction?, pollingProperties
ContinueSameStep—
OAuthOAuthAuthorizationResponseStepoauthAuthorizationResponseProperties (carries code)
Client operation (conform to ClientOperationStep)ExternalBrowserClientOperationStep—
BankIdClientOperationStepactivationLink?
EncapClientOperationStep—
WebAuthnRegistrationClientOperationStep—
WebAuthnAuthenticationClientOperationStep—
GenericClientOperationStep—
FallbackGenericRepresentationStepproperties?

Switching on the result#

import IdsvrHaapiSdk

haapiManager.start { result in
    switch result {
    case .representation(let representation):
        handleRepresentation(representation)
    case .problem(let problem):
        handleProblem(problem)
    case .error(let error):
        // Cast to HaapiError for the typed hierarchy — see Error Handling.
        handleError(error)
    }
}

private func handleRepresentation(_ representation: HaapiRepresentation) {
    switch representation {
    case let step as AuthenticatorSelectorStep:
        // step.title.literal, step.authenticators
        // Each AuthenticatorOption carries an Action.Form for selection.
        break
    case let step as InteractiveFormStep:
        // step.actions includes form actions; render their fields as a UIKit form.
        break
    case let step as PollingStep:
        // step.pollingProperties.status, step.mainAction, step.cancelAction
        break
    case let step as OAuthAuthorizationResponseStep:
        // Flow complete — extract the authorization code.
        let code = step.oauthAuthorizationResponseProperties.code
        break
    case let step as ClientOperationStep:
        // Delegate to the platform (BankID, WebAuthn, external browser, …)
        break
    default:
        // GenericRepresentationStep, ContinueSameStep, UserConsentStep, RedirectionStep.
        break
    }
}

Actions#

Actions represent operations the user can perform. They live under the Action enum:

CaseactionTypeCarriesWhen to use
Action.form(FormAction).formFormActionModelThe most common action — render fields, submit values
Action.selector(SelectorAction).selector[Action] (nested options)Server provides a choice (e.g., device selector); each option is itself an Action
Action.clientOperation(ClientOperationAction).clientOperationA ClientOperationActionModelExternal handling required (BankID, WebAuthn, browser launch)

Every action also carries:

FieldTypeDescription
kindActionKindSemantic meaning: .login, .cancel, .redirect, .poll, etc. (17 values)
titleMessage?Display title for buttons / labels

FormAction#

Submitting a form is the SDK’s primary write operation:

let formAction: FormActionModel = …

let parameters: [String: Any] = [
    formAction.fields[0].name: "user@example.com",
    formAction.fields[1].name: "secret"
]

haapiManager.submitForm(formAction, parameters: parameters) { result in
    handleResult(result)
}

submitForm’s parameters accept any JSON-serialisable value for non-GET methods; GET form actions require [String: String]. See HAAPI Flow for the full parameter-type semantics across platforms.

The FormActionModel carries:

FieldTypeDescription
fields[FormField]Form fields to render
methodStringHTTP method (e.g., "POST")
hrefStringTarget URI
actionTitleMessage?Submit button text

ClientOperationAction#

Six sub-types, each with its own model fields: ExternalBrowser, BankId, Encap, WebAuthnRegistration, WebAuthnAuthentication, Generic. Each model includes continueActions and errorActions arrays for post-operation flow.

Completing a WebAuthn Step#

WebAuthnRegistrationClientOperationStep and WebAuthnAuthenticationClientOperationStep carry the options for a WebAuthn ceremony. The SDK does not run the ceremony: use any WebAuthn driver, such as ASAuthorizationController, a passkey library, or a vendor SDK. Pass the raw values of the driver’s response to the step’s buildParameters, and submit the result with the step’s continueFormActionModel.

Registration takes the option set the ceremony used, actionModel.platformOptions or actionModel.crossPlatformOptions:

let parameters = try registrationStep.buildParameters(publicKey: options,
                                                      clientDataJSON: registration.rawClientDataJSON,
                                                      attestationObject: attestationObject,
                                                      type: "public-key",
                                                      rawId: registration.credentialID)
let result = await haapiManager.submitForm(registrationStep.continueFormActionModel, parameters: parameters)

Authentication takes the assertion’s values:

let parameters = try authenticationStep.buildParameters(authenticatorData: assertion.rawAuthenticatorData,
                                                        clientDataJSON: assertion.rawClientDataJSON,
                                                        signature: assertion.signature,
                                                        userHandle: assertion.userID,
                                                        rawId: assertion.credentialID,
                                                        type: "public-key")
let result = await haapiManager.submitForm(authenticationStep.continueFormActionModel, parameters: parameters)
  • The values are raw bytes, and the SDK base64url-encodes them. A driver that returns the WebAuthn JSON form, with base64url strings such as response.clientDataJSON, needs them decoded first. type is public-key for WebAuthn credentials.
  • The authenticator attachment sent to the Curity Identity Server is always the one of the option set passed as publicKey. A credential the driver reports as cross-platform, such as a passkey used from a nearby phone, is still registered as a platform device, so later logins offer it through the platform authenticator.
  • A nil userHandle is left out of the parameters. A discoverable-credential login needs it, because the server identifies the user from it.
  • buildParameters throws an IdsvrError when the step’s continue form has no context field for the ceremony or its options are not from a WebAuthn step, and logs the cause under the HAAPI_SDK_MAPPING follow-up tag.
  • The method, its parameters, and its output match buildParameters in the Android SDK.

Passkeys need an Associated Domains entitlement. The app’s webcredentials: domain must match the relying party, or the ceremony fails before buildParameters is reached. See iOS Platform Notes.

The earlier formattedParametersForRegistration and formattedParametersForAssertion are deprecated; Upgrade — iOS WebAuthn buildParameters maps each to buildParameters.

Form Fields#

FormField is a Swift enum with 7 cases:

Common fields#

FieldTypeDescription
nameStringParameter key for submission
valueString?Current / default value
labelMessage?Display label
placeholderMessage?Placeholder text
isRequiredBoolWhether the field is required

Rendering guide#

FormField caseUIKit elementNotes
.hiddennoneInclude value in submit params automatically — don’t render
.textUITextFieldInspect kind for keyboard type (.email, .tel, etc.)
.usernameUITextFieldautocapitalizationType = .none
.passwordUITextFieldisSecureTextEntry = true
.selectUIPickerView or UIButton setRender options as choices
.checkboxUISwitchRespect readOnly flag
.contextnoneHidden context data (e.g., WebAuthn challenge) — don’t render

Problems#

Problem responses follow RFC 7807. iOS uses a Problem protocol with three concrete types:

TypeproblemTypeExtra fields
Problem (base)base value(base only)
InvalidInputProblem.invalidInputinvalidFields: [InvalidField] (each: name, reason?, detail: Message), errorDescription?
AuthorizationProblem.authorizationerror, errorDescription?, iss?

InvalidInputProblem.invalidFields is the right hook for showing field-level validation errors inline in your form.

Token Responses#

OAuthTokenManager.fetchAccessToken, refreshAccessToken, and revokeRefreshToken return a TokenResponse enum. Match its cases:

CaseFields
.successfulToken(SuccessfulTokenResponse)accessToken, tokenType?, scope?, expiresIn, refreshToken?, idToken?
.errorToken(ErrorTokenResponse)error, errorDescription?, errorUri?
.error(Error)Framework / transport error

See OAuthTokenManager for the full lifecycle, and Error Handling for the difference between RFC-6749 OAuth errors (values) and framework errors.

Each Link carries:

FieldTypeDescription
hrefStringTarget URI
relStringRelationship type (e.g., forgot-password)
titleMessage?Display text
typeString?Hint for the media type of the linked resource

Render link sets alongside the main form — typically as secondary buttons or text links.

Classify before acting#

Not every link is a HAAPI step. Read link.kind and act on the result rather than following every link:

LinkKindMeaningWhat to do
.haapiA HAAPI resourceawait haapiManager.followLink(link)
.inlineImageAn image, such as a BankID QR codeRender it in place; never follow it
.externalContent for the platformHand href to the system browser or another app
switch link.kind {
case .haapi:
    let result = await haapiManager.followLink(link)
    // handle result as any other step
case .inlineImage:
    render(link.href)          // e.g. a data: QR image
case .external:
    guard let url = URL(string: link.href) else { return }
    UIApplication.shared.open(url)
}

kind is derived from rel and type only — it is pure, performs no network access, and never inspects the link’s origin. The rules are evaluated in order:

  1. a type of image/* is .inlineImage;
  2. a rel of download or authorization-response is .external;
  3. a scheme other than http/https is .external;
  4. any other declared type is .external;
  5. otherwise — including when type is absent — it is .haapi.

A link with no type is a HAAPI resource, per the HAAPI data model, and must be fetched with an authenticated request. Do not treat a missing type as a reason to compare the link’s origin against your base URL.

followLink additionally refuses a link whose target is not the configured identity server, comparing scheme and host. An ASCII host is compared as the DPoP proof names it, so spellings that differ only in letter case or percent-encoding are the same server. A host containing a non-ASCII character matches nothing, so configure an internationalized host in its ASCII (xn--) form. It returns HaapiErrorCode.nonHaapiLink and sends nothing — see Error Handling.

If you implement the UI layer yourself, the UIKit framework exposes the same classification on LinkItemModel.kind — see UI Extensibility.

Related: HAAPI Flow · OAuthTokenManager · Error Handling · iOS SDK · iOS Reference (DocC for the full type signatures)

Was this helpful?