Documentation
/
SDK Reference
/
Frontend
/
iOS
/
Hawcx iOS SDK (V6)

Hawcx iOS SDK (V6)

Integrate Hawcx V6 on iOS with an adaptive authentication flow, MFA, and trusted-device support.

Overview

Hawcx V6 for iOS gives you a prompt-driven authentication flow that adapts to the authentication methods configured for your project. Your app starts a flow, renders the next prompt Hawcx returns, and completes sign-in by exchanging the resulting authorization code on your backend.

This is the recommended iOS integration path for new projects using Hawcx V6.

V6 docs, current SDK API names

V6 is the public SDK release name. The current iOS API surface uses HawcxV1SDK and related HawcxV1* types because the SDK is built on Hawcx's protocol v1 flow.

Already using the existing V5 SDK?

If your app is already integrated with the existing HawcxSDK V4/V5 flow, continue using the V5 iOS guide. This page is for the new V6 integration path only.

Which Guide Should You Use?

Use this V6 guide if:

  1. you are starting a new iOS integration
  2. you want an adaptive flow with backend-selected primary, MFA, and trusted-device steps
  3. you are implementing the current Hawcx iOS release path in your app

Use the V5 iOS guide if:

  1. your app already ships the existing HawcxSDK V4/V5 integration
  2. you still depend on the older authenticateV4 / submitOtpV4 flow
  3. you are maintaining an existing production integration and are not ready to migrate yet

Incremental migration is supported

If you are migrating gradually, HawcxV1SDK can own the new V6 auth flow while HawcxSDK continues to power existing utilities such as V4/V5 auth flows, push approvals, device sessions, or older token helpers.

Before You Begin

Before integrating the iOS SDK, make sure your Hawcx project is configured in the Hawcx dashboard:

  1. Create or select your project
  2. Configure the primary authentication step
  3. Configure MFA and trusted-device behavior if needed
  4. Generate a Config ID for the SDK

For product configuration details, see:

  1. Projects
  2. Config IDs
  3. Auth Flow Configuration

How the V6 Flow Works

  1. Your app initializes HawcxV1SDK with a configId and the Hawcx protocol base URL.
  2. You start the flow with a HawcxV1FlowType, usually .signin, plus the user's identifier.
  3. The backend responds with the next step for that user and project configuration.
  4. Your app renders the current prompt:
    • choose a method
    • enter an OTP
    • set up SMS
    • set up TOTP
    • continue a redirect
    • wait for approval
  5. When the flow completes, the SDK returns authCode and, when applicable, codeVerifier.
  6. Your backend exchanges the code and creates the user session.

Integration Responsibilities

Your app is responsible for:

  1. collecting the user's identifier
  2. rendering the current prompt
  3. sending the user's input back to the SDK
  4. handling redirects when prompted
  5. sending the final authorization code to your backend

The SDK handles:

  1. protocol requests and required headers
  2. PKCE generation when needed
  3. trusted-device storage and secure device credentials
  4. internal device-trust processing
  5. approval polling when the flow requires it

Your backend handles:

  1. exchanging the authorization code
  2. verifying returned claims
  3. creating the app session
  4. returning the result your app needs to continue

What the SDK handles automatically

The SDK manages the protocol details for you, including required headers, PKCE when needed, secure trusted-device storage, and internal device-trust processing.

Requirements

  1. iOS 17.0+
  2. Swift 5.9+
  3. A Hawcx Config ID for your project
  4. Your Hawcx tenant base URL

Example base URL:

https://stage-api.hawcx.com

You can pass either the tenant host root or an existing /v1 URL. The SDK normalizes the base URL to the correct protocol route automatically. Do not append /auth yourself.

Install

Swift Package Manager

In Xcode:

  1. Go to File -> Add Package Dependencies...
  2. Enter: https://github.com/hawcx/hawcx_ios_sdk.git
  3. Choose Up to Next Major Version
  4. Set the version to 6.0.2
  5. Select the HawcxFramework product

Using Package.swift directly:

dependencies: [
    .package(url: "https://github.com/hawcx/hawcx_ios_sdk.git", from: "6.0.2")
]

Manual XCFramework

  1. Download HawcxFramework.xcframework.zip from the latest V6 release
  2. Unzip the archive
  3. Drag HawcxFramework.xcframework into your Xcode project
  4. Set it to Embed & Sign

Initialize the SDK

import HawcxFramework

let hawcx = HawcxV1SDK(
    configId: "<YOUR_CONFIG_ID>",
    baseURL: URL(string: "https://stage-api.hawcx.com")!,
    primaryMethodSelectionPolicy: .automaticFromIdentifier
)

Initialization Notes

  1. configId is the Hawcx value provisioned for this integration. In current public releases, this is the same value you may receive as your project API key / Config ID.
  2. baseURL should point to your Hawcx tenant host. The SDK accepts either the host root or an existing /v1 path and normalizes it automatically.
  3. primaryMethodSelectionPolicy defaults to .manual.
  4. autoPollApprovals defaults to true, which is the right choice for most apps.
  5. relyingParty is optional. Set it only when your backend expects an X-Relying-Party header for this integration.
  6. Use .automaticFromIdentifier if you want the SDK to auto-select the primary method during .signin when the backend returns the initial primary method-selection step and the identifier already makes the correct email or phone path obvious.

Build an Auth Coordinator

The recommended integration shape is to keep one coordinator or view model responsible for:

  1. holding the current HawcxV1FlowUpdate
  2. calling start(...)
  3. reacting to flow.onUpdate
  4. forwarding user input back to the SDK
import HawcxFramework
import Foundation

@MainActor
final class AuthCoordinator: ObservableObject {
    @Published private(set) var update: HawcxV1FlowUpdate = .idle

    let sdk: HawcxV1SDK

    init() {
        sdk = HawcxV1SDK(
            configId: "<YOUR_CONFIG_ID>",
            baseURL: URL(string: "https://stage-api.hawcx.com")!,
            primaryMethodSelectionPolicy: .automaticFromIdentifier
        )

        sdk.flow.onUpdate = { [weak self] update in
            Task { @MainActor in
                self?.update = update
            }
        }
    }

    func start(identifier: String) {
        sdk.start(flowType: .signin, identifier: identifier)
    }

    func selectMethod(_ methodId: String) {
        sdk.flow.selectMethod(methodId)
    }

    func submitCode(_ code: String) {
        sdk.flow.submitCode(code)
    }

    func submitTotp(_ code: String) {
        sdk.flow.submitTotp(code)
    }

    func submitPhone(_ phone: String) {
        sdk.flow.submitPhone(phone)
    }

    func resend() -> Bool {
        sdk.flow.resend()
    }

    func cancel() {
        sdk.flow.cancel()
    }

    func reset() {
        sdk.reset()
    }
}

Start Authentication

HawcxV1FlowType supports:

  1. .signin
  2. .signup
  3. .accountManage

Most apps should start with .signin:

coordinator.start(identifier: "[email protected]")

Use additional fields only when your tenant policy requires them:

sdk.start(
    flowType: .signin,
    identifier: "[email protected]",
    startToken: "<optional-start-token>",
    inviteCode: "12345678"
)

startToken is optional and is only needed when your backend or hosted flow gives your app a pre-issued token to resume or continue a specific start path.

You can also pass codeChallenge, but in most cases you do not need to. If you omit it, the SDK automatically generates PKCE and returns the codeVerifier in the completed update.

Handle Flow Updates

flow.onUpdate emits a HawcxV1FlowUpdate. Your app should switch on that state and render the appropriate UI.

Top-Level Flow States

UpdateWhat it means
idleNo active flow
loadingA request is in flight
promptThe backend is asking the user to complete a step
completedAuthentication succeeded and returned an authCode
errorThe flow failed or needs recovery

User-Visible Prompt Types

PromptWhat to showNext SDK call
selectMethodMethod pickerflow.selectMethod(...)
enterCodeOTP inputflow.submitCode(...)
setupSmsPhone number input or confirmationflow.submitPhone(...)
setupTotpTOTP setup UI with secret / QRflow.submitTotp(...)
enterTotpTOTP code inputflow.submitTotp(...)
redirectButton to continue in browser / hosted flowflow.oauthCallback(...) after callback
awaitApprovalWaiting UI, optionally with QR infoUsually no action; poll if needed

Internal prompts are not app work

Some protocol steps are internal and should not be rendered in your UI. The SDK handles those steps automatically.

func handle(update: HawcxV1FlowUpdate) {
    switch update {
    case .idle:
        // Show identifier input

    case .loading:
        // Show loading state

    case let .prompt(context, prompt):
        let traceId = context.meta.traceId
        let stepLabel = context.stepInfo?.label
        let riskMessage = context.risk?.message

        switch prompt {
        case let .selectMethod(methods, _):
            // Render available methods
            print(methods.map(\.id))

        case let .enterCode(destination, _, _, _, resendAt):
            // Render OTP UI and resend countdown
            print(destination, resendAt ?? "")

        case let .setupSms(existingPhone):
            // Collect or confirm phone number
            print(existingPhone ?? "")

        case let .setupTotp(secret, otpauthUrl, _):
            // Render authenticator setup UI
            print(secret, otpauthUrl)

        case .enterTotp:
            // Render TOTP code input
            break

        case let .redirect(url, returnScheme):
            // Open hosted step and resume through oauthCallback(...)
            print(url, returnScheme ?? "")

        case let .awaitApproval(qrData, expiresAt, pollInterval):
            // Show waiting UI
            print(qrData ?? "", expiresAt, pollInterval)
        }

        print(traceId, stepLabel ?? "", riskMessage ?? "")

    case let .completed(session, authCode, expiresAt, codeVerifier, _):
        // Send authCode + codeVerifier to your backend
        // Capture session for correlation or support logs if useful
        print(session, authCode, expiresAt, codeVerifier ?? "")

    case let .error(_, code, action, message, retryable, details, meta):
        // Surface a user-friendly error and capture traceId for support
        print(code, action as Any, message, retryable, details as Any, meta?.traceId ?? "")
    }
}

Use Step Labels and Risk Metadata

Each prompt context may include extra metadata:

  1. context.stepInfo
    • helps you label the current stage, such as Verify Identity or Second Factor
  2. context.risk
    • helps you show a step-up or risk warning if the backend flags the session
  3. context.meta.traceId
    • should be captured in logs or support tickets for debugging

These are optional but strongly recommended for a polished production UI.

Resend Codes

For email OTP and SMS OTP steps, call:

let didResend = sdk.flow.resend()

If didResend is false, the resend window is not open yet. Use the resendAt value from the current enterCode prompt to drive your countdown or “try again later” UI.

Handle Redirect Steps

If the backend returns a redirect prompt, open the hosted flow in an ASWebAuthenticationSession, parse the callback, and resume the Hawcx flow:

import AuthenticationServices
import HawcxFramework
import UIKit

let redirectSession = HawcxV1OAuthRedirectSession {
    UIApplication.shared.connectedScenes
        .compactMap { $0 as? UIWindowScene }
        .flatMap(\.windows)
        .first(where: \.isKeyWindow) ?? ASPresentationAnchor()
}

func handleRedirect(url: String, returnScheme: String) {
    guard let redirectURL = URL(string: url) else { return }

    redirectSession.start(url: redirectURL, callbackScheme: returnScheme) { result in
        switch result {
        case let .success(callbackURL):
            if let callback = HawcxV1OAuthCallbackParser.parse(callbackURL) {
                sdk.flow.oauthCallback(code: callback.code, state: callback.state)
            }
        case .failure:
            break
        }
    }
}

Handle Completion and Backend Exchange

When the flow completes, the SDK returns the values your app needs to finish sign-in with your backend:

  1. session
  2. authCode
  3. expiresAt
  4. codeVerifier when PKCE was generated by the SDK
  5. meta.traceId for correlation and support logs

Use authCode and codeVerifier as the primary inputs for the backend exchange. expiresAt is useful for timing, telemetry, or support logs, but it is not the core exchange input. session and meta.traceId are optional context values you can pass through for correlation or support tooling.

Include codeVerifier whenever the SDK returns it. If your app supplied a custom codeChallenge, send the matching verifier from that flow to your backend as part of the exchange.

Send the completion payload to your backend immediately over HTTPS. Do not perform the code exchange from the iOS app.

Recommended payload shape:

{
  "authCode": "<authCode>",
  "codeVerifier": "<optional-codeVerifier>",
  "identifier": "[email protected]",
  "session": "<optional-session>"
}

Add any app-specific context your backend needs, but keep the authorization code exchange and token verification on the server.

You usually do not need to send session to your backend unless you want it for correlation, analytics, or support logs.

Your backend should:

  1. exchange authCode and codeVerifier with the Hawcx backend SDK
  2. verify the returned claims
  3. create your app session or tokens
  4. return the app auth result your iOS app needs

Recommended pattern

For most production apps, backend code exchange is the right default. Keep OAuth client credentials and token verification logic on your server.

iOS app-to-backend example

In this example, identifier is the same email, username, or phone number you used to start the Hawcx flow.

struct HawcxExchangeRequest: Encodable {
    let authCode: String
    let codeVerifier: String?
    let identifier: String
    let session: String?
}

func exchangeWithBackend(_ payload: HawcxExchangeRequest) async throws {
    var request = URLRequest(url: URL(string: "https://your-backend.example.com/api/hawcx/exchange")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try JSONEncoder().encode(payload)

    let (_, response) = try await URLSession.shared.data(for: request)
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw URLError(.badServerResponse)
    }
}

switch update {
case let .completed(session, authCode, _, codeVerifier, _):
    Task {
        do {
            try await exchangeWithBackend(
                HawcxExchangeRequest(
                    authCode: authCode,
                    codeVerifier: codeVerifier,
                    identifier: identifier,
                    session: session
                )
            )
        } catch {
            // Show an error state or retry option in your app.
        }
    }
default:
    break
}

This example shows the app-to-backend handoff only. Your backend then uses the Hawcx backend SDK to exchange and verify the code before it creates the user session.

Backend implementation guides:

Optional SDK-Managed Exchange

If your app truly needs an SDK-managed exchange path, the framework also provides HawcxV1OAuthExchangeService. Treat that as an advanced option rather than the default integration path. When used, it verifies the returned ID token with the public key in your HawcxOAuthConfig and returns a HawcxV1OAuthToken (idToken, tokenType, expiresIn) rather than a V5-style access / refresh token pair.

Trusted Devices and Secure Storage

V6 automatically manages trusted-device material for you:

  1. Device trust prompts are handled internally by the SDK
  2. Device credentials are stored per app, per user, and per Hawcx project / Config ID
  3. Secure storage is isolated so the same user in two different apps does not share credentials
  4. If local device-trust material becomes invalid, the SDK clears it and re-enrolls on the next flow when needed

Your UI does not need to implement the crypto flow directly.

Reset, Cancel, and Recovery

Use these methods for flow control:

sdk.flow.cancel()  // cancel the active flow
sdk.reset()        // clear in-memory flow state and return to idle

What reset() Does

  1. Clears the current flow session and prompt state
  2. Stops any in-progress approval polling
  3. Returns the flow to idle

What reset() Does Not Do

  1. It does not log the user out of your backend
  2. It does not revoke your app session
  3. It does not automatically remove trusted-device material

For app logout, clear your own app session or backend-issued tokens. If you also use existing Hawcx utilities such as push approvals, device sessions, or token-storage helpers, those remain on HawcxSDK and can continue to coexist with HawcxV1SDK.

Error Handling

When the flow emits .error(...), use:

  1. code for programmatic handling
  2. message for a user-visible summary
  3. action to decide whether to retry, restart, wait, resend, or abort
  4. details.retryAt / details.retryAfterSeconds for timing
  5. meta.traceId for support and debugging

Common actions include:

Error ActionRecommended UI behavior
retryInputLet the user correct the current input
restartFlowReturn to identifier entry and start again
waitShow countdown or disabled state
retryRequestRetry automatically or let the user retry
resendCodeOffer resend
selectMethodReturn to method selection
abortEnd the flow and show failure

Troubleshooting

401 / 403 / Unauthorized

Check:

  1. configId is correct
  2. baseURL points to the correct Hawcx tenant host or /v1 route
  3. the project is configured correctly in the Hawcx dashboard

OTP Resend Is Disabled

Your app is likely still inside the resend cooldown window. Use resendAt from the current enterCode prompt and wait until the timer expires.

Device Trust Keeps Re-Enrolling

If the local trusted-device record becomes invalid or the device environment changes, the SDK may clear the local record and re-enroll. This is expected recovery behavior.

I Need Better Support Logs

Always capture traceId from context.meta.traceId or meta.traceId when reporting an issue to Hawcx support.

Existing Utility APIs

This V6 page focuses on the new prompt-driven authentication flow. If your app still uses existing Hawcx utilities such as:

  1. V4 or V5 Smart Connect flows
  2. push approval handling
  3. device session APIs
  4. existing HawcxSDK token-storage helpers

those remain available on HawcxSDK and can coexist with HawcxV1SDK during migration. For the full V5 Smart Connect integration path, use the V5 iOS guide.