Skip to main content

iOS Integration

🍎 iOS Integration​

The SDK is Kotlin Multiplatform, so the same commonMain flow (fetchConfig → buildSolution → login) is exposed to iOS through a generated Kotlin/Native framework you import from Swift.

⚠️ Current status — structured, not yet validated. The iOS target compiles and the public API is callable from Swift, but the platform pieces are stubs: identity falls back to the uuid tier and every collector reports as unavailable. The login round‑trip works end‑to‑end; the device signals are simply empty until the native collectors and Secure Enclave identity are implemented. Use this page to wire the app now and to understand the contract a full port will fulfill.


1. Build / import the framework​

Produce the Kotlin/Native framework from the SDK module and link it in Xcode (directly, or via CocoaPods / Swift Package Manager once configured):

# Simulator (Apple Silicon)
./gradlew :sdk:linkDebugFrameworkIosSimulatorArm64
# Device
./gradlew :sdk:linkReleaseFrameworkIosArm64

The framework exposes the io.guardiankey.gkas API to Swift. Kotlin suspend functions are bridged to Swift async functions (or completion handlers).


2. Create the SDK instance​

On iOS the PlatformContext is empty (no Android Context is needed). Build the SDK and call the same three steps.

import shared   // the Kotlin/Native framework name

let config = GkasSdkConfig(
backendBaseUrl: "https://your-backend.example.com/api",
httpTimeoutMs: 8000,
debug: false
)

let gkas = GuardianKeyGkas.companion.create(
config: config,
platform: PlatformContext()
)

3. Drive the login​

func login(username: String, password: String) async {
do {
// 1) GET /config
let config = try await gkas.fetchConfig()

// 2) collect signals + resolve/sign identity → gkas_solution (Base64)
let solution = try await gkas.buildSolution(config: config, fieldValue: username)

// 3) POST /login
let request = LoginRequest(
username: username,
password: password,
gkasSolution: solution,
config: config
)
let result = try await gkas.login(request: request)

switch result {
case let allowed as LoginResultAllowed:
goToHome()
case let blocked as LoginResultBlocked:
showBlocked(blocked.reason) // stay on login
case let error as LoginResultError:
showError(error.message)
default:
break
}
} catch {
showError("Communication failure: \(error)")
}
}

The contract is identical to Android, and the backend returns a normalized ALLOW / BLOCK decision.


4. What a full iOS port adds​

The structured stubs in iosMain mark where the native work goes. A complete iOS implementation provides two things:

  • Native device identity — backing the software and biometric tiers with the platform's secure hardware (Secure Enclave) and LocalAuthentication for the biometric prompt, instead of the current uuid fallback.
  • Native signal collectors — the iOS counterparts of the device/behavioral signals, so the same information the Android build contributes is also gathered on iOS.

Until those are implemented, identity falls back to the uuid tier and the signals report as unavailable; the login round‑trip still works.

Like Android, each collector must remain best‑effort and never throw — when a permission or capability is missing it reports unavailable and login proceeds. A full port also declares the matching Info.plist usage descriptions and entitlements; without them the corresponding collectors stay unavailable by design.


See the API reference for the shared public API and the backend contract.