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:
- you are starting a new iOS integration
- you want an adaptive flow with backend-selected primary, MFA, and trusted-device steps
- you are implementing the current Hawcx iOS release path in your app
Use the V5 iOS guide if:
- your app already ships the existing
HawcxSDKV4/V5 integration - you still depend on the older
authenticateV4/submitOtpV4flow - 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:
- Create or select your project
- Configure the primary authentication step
- Configure MFA and trusted-device behavior if needed
- Generate a Config ID for the SDK
For product configuration details, see:
How the V6 Flow Works
- Your app initializes
HawcxV1SDKwith aconfigIdand the Hawcx protocol base URL. - You start the flow with a
HawcxV1FlowType, usually.signin, plus the user's identifier. - The backend responds with the next step for that user and project configuration.
- Your app renders the current prompt:
- choose a method
- enter an OTP
- set up SMS
- set up TOTP
- continue a redirect
- wait for approval
- When the flow completes, the SDK returns
authCodeand, when applicable,codeVerifier. - Your backend exchanges the code and creates the user session.
Integration Responsibilities
Your app is responsible for:
- collecting the user's identifier
- rendering the current prompt
- sending the user's input back to the SDK
- handling redirects when prompted
- sending the final authorization code to your backend
The SDK handles:
- protocol requests and required headers
- PKCE generation when needed
- trusted-device storage and secure device credentials
- internal device-trust processing
- approval polling when the flow requires it
Your backend handles:
- exchanging the authorization code
- verifying returned claims
- creating the app session
- 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
- iOS 17.0+
- Swift 5.9+
- A Hawcx Config ID for your project
- Your Hawcx tenant base URL
Example base URL:
https://stage-api.hawcx.comYou 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:
- Go to File -> Add Package Dependencies...
- Enter:
https://github.com/hawcx/hawcx_ios_sdk.git - Choose Up to Next Major Version
- Set the version to
6.0.2 - Select the
HawcxFrameworkproduct
Using Package.swift directly:
dependencies: [
.package(url: "https://github.com/hawcx/hawcx_ios_sdk.git", from: "6.0.2")
]Manual XCFramework
- Download
HawcxFramework.xcframework.zipfrom the latest V6 release - Unzip the archive
- Drag
HawcxFramework.xcframeworkinto your Xcode project - 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
configIdis 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.baseURLshould point to your Hawcx tenant host. The SDK accepts either the host root or an existing/v1path and normalizes it automatically.primaryMethodSelectionPolicydefaults to.manual.autoPollApprovalsdefaults totrue, which is the right choice for most apps.relyingPartyis optional. Set it only when your backend expects anX-Relying-Partyheader for this integration.- Use
.automaticFromIdentifierif you want the SDK to auto-select the primary method during.signinwhen 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:
- holding the current
HawcxV1FlowUpdate - calling
start(...) - reacting to
flow.onUpdate - 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:
.signin.signup.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
| Update | What it means |
|---|---|
idle | No active flow |
loading | A request is in flight |
prompt | The backend is asking the user to complete a step |
completed | Authentication succeeded and returned an authCode |
error | The flow failed or needs recovery |
User-Visible Prompt Types
| Prompt | What to show | Next SDK call |
|---|---|---|
selectMethod | Method picker | flow.selectMethod(...) |
enterCode | OTP input | flow.submitCode(...) |
setupSms | Phone number input or confirmation | flow.submitPhone(...) |
setupTotp | TOTP setup UI with secret / QR | flow.submitTotp(...) |
enterTotp | TOTP code input | flow.submitTotp(...) |
redirect | Button to continue in browser / hosted flow | flow.oauthCallback(...) after callback |
awaitApproval | Waiting UI, optionally with QR info | Usually 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.
Recommended Prompt Handling Pattern
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:
context.stepInfo- helps you label the current stage, such as Verify Identity or Second Factor
context.risk- helps you show a step-up or risk warning if the backend flags the session
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:
sessionauthCodeexpiresAtcodeVerifierwhen PKCE was generated by the SDKmeta.traceIdfor 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:
- exchange
authCodeandcodeVerifierwith the Hawcx backend SDK - verify the returned claims
- create your app session or tokens
- 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:
- Device trust prompts are handled internally by the SDK
- Device credentials are stored per app, per user, and per Hawcx project / Config ID
- Secure storage is isolated so the same user in two different apps does not share credentials
- 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 idleWhat reset() Does
- Clears the current flow session and prompt state
- Stops any in-progress approval polling
- Returns the flow to
idle
What reset() Does Not Do
- It does not log the user out of your backend
- It does not revoke your app session
- 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:
codefor programmatic handlingmessagefor a user-visible summaryactionto decide whether to retry, restart, wait, resend, or abortdetails.retryAt/details.retryAfterSecondsfor timingmeta.traceIdfor support and debugging
Common actions include:
| Error Action | Recommended UI behavior |
|---|---|
retryInput | Let the user correct the current input |
restartFlow | Return to identifier entry and start again |
wait | Show countdown or disabled state |
retryRequest | Retry automatically or let the user retry |
resendCode | Offer resend |
selectMethod | Return to method selection |
abort | End the flow and show failure |
Troubleshooting
401 / 403 / Unauthorized
Check:
configIdis correctbaseURLpoints to the correct Hawcx tenant host or/v1route- 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:
- V4 or V5 Smart Connect flows
- push approval handling
- device session APIs
- existing
HawcxSDKtoken-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.