Configuration (UI Layer)#

The UI Layer’s configuration object — HaapiUIKitConfiguration on iOS, WidgetConfiguration on Android — carries every parameter the framework needs to connect to the Curity Identity Server, render the flow, and (when configured) bind tokens, fall back to Dynamic Client Registration, or collect risk-assessment context. Build it once at application startup and inject it through HaapiUIKitApplication (iOS) or HaapiUIWidgetApplication (Android).

For presentation knobs (modal vs stack, polling interval, selector style), see Presentation Options. For starting the flow itself, see Flow Lifecycle.

Minimal Configuration#

The five required parameters are the same on both platforms: client ID, base URL, token endpoint, authorization endpoint, and the app redirect URI. Everything else is optional.

Build HaapiUIKitConfiguration once in AppDelegate.application(_:didFinishLaunchingWithOptions:) and use it to construct HaapiUIKitApplication:

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var haapiUIKitConfiguration = HaapiUIKitConfigurationBuilder(
        clientId: Constants.clientId,
        baseUrl: Constants.baseURL,
        tokenEndpointUrl: Constants.tokenEndpointURL,
        authorizationEndpointUrl: Constants.authorizationEndpointURL,
        appRedirect: Constants.appRedirectURIString
    ).build()

    var haapiUIKitApplication: HaapiUIKitApplication!

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

clientId — the OAuth client ID configured on the Curity Identity Server with the haapi capability.

baseUrl — the base URL of the Curity Identity Server (the runtime endpoint base, typically the issuer URL).

tokenEndpointUrl — the OAuth token endpoint, typically <baseUrl>/oauth/v2/oauth-token.

authorizationEndpointUrl — the OAuth authorization endpoint, typically <baseUrl>/oauth/v2/oauth-authorize.

appRedirect — the app redirect URI registered on the OAuth client (custom scheme or universal link).

For SwiftUI apps, build the configuration in AppDelegate the same way and hook it with @UIApplicationDelegateAdaptor.

Implement HaapiUIWidgetApplication on your Application subclass and expose widgetConfiguration from it:

class ClientApplication : Application(), HaapiUIWidgetApplication {
    private val baseUri = URI.create("https://idsvr.example.com")

    override val widgetConfiguration: WidgetConfiguration
        get() = WidgetConfiguration.Builder(
            clientId = "my-client-id",
            baseUri = baseUri,
            tokenEndpointUri = baseUri.resolve("/oauth/v2/oauth-token"),
            authorizationEndpointUri = baseUri.resolve("/oauth/v2/oauth-authorize"),
            appRedirect = "app://haapi"
        ).build()
}

clientId — the OAuth client ID configured on the Curity Identity Server with the haapi capability.

baseUri — java.net.URI for the Curity Identity Server runtime base.

tokenEndpointUri — the OAuth token endpoint.

authorizationEndpointUri — the OAuth authorization endpoint.

appRedirect — the app redirect URI registered on the OAuth client.

Register ClientApplication in AndroidManifest.xml via android:name=".ClientApplication" and also register HaapiFlowActivity:

<application android:name=".ClientApplication" ...>
    <activity android:name="se.curity.identityserver.haapi.android.ui.widget.HaapiFlowActivity" />
    <!-- ... -->
</application>

Security Configuration#

The five security knobs — DCR fallback, Token Binding, Client Authentication, Risk Assessment, token revocation — are wired on the UI-Layer builder using the same parameter types as at the SDK Layer. The SDK-Layer pages carry the per-parameter depth; the UI-Layer call sites are:

KnobiOS builder callAndroid builder callParameter depth
DCR fallbacksetDCRConfiguration(configuration:)setDcr(...)DCR
Token BindingsetTokenBoundConfiguration(configuration:)setDpopTokenBinding(...)Token Binding
Client AuthenticationsetClientAuthenticationMethod(method:)setClientAuthenticationMethod(...)Client Authentication
Risk AssessmentsetRiskAssessmentConfiguration(_:)setContext(...) ¹Risk Assessment
Token revocationsetRevocationEndpointUrl(endpoint:)setRevocationEndpointUri(...)Token Lifecycle

¹ On Android, risk assessment is wired by passing the application Context rather than a dedicated configuration object — setContext(applicationContext) is the wiring call. The framework then reads OS name, OS version, device model, and package name from that Context internally and forwards them on every HAAPI request. iOS exposes the same four fields as explicit parameters on RiskAssessmentConfiguration because UIDevice is main-actor-isolated; see Risk Assessment for the field-by-field detail on both platforms.

Example — configuring DCR fallback + a Secret client authentication method on the UI-Layer builder (chain these onto the foundational builder shown above):

// ... required parameters from the Minimal Configuration block above
.setClientAuthenticationMethod(
    method: ClientAuthenticationMethodSecret("foo")
)
.setDCRConfiguration(
    configuration: DCRConfiguration(
        templateClientId: "dcr-template-client-haapi-ios",
        clientRegistrationEndpointUrl: URL(
            string: "/oauth/oauth-registration",
            relativeTo: baseURL
        )
    )
)
.build()
// ... required parameters from the Minimal Configuration block above
.setClientAuthenticationMethod(
    WidgetConfiguration.ClientAuthenticationMethod.Secret("my-secret")
)
.setDcr(
    WidgetConfiguration.Dcr(
        templateClientId = "dcr-template-client-haapi-android",
        clientRegistrationEndpointUri = baseUri.resolve("/oauth/oauth-registration"),
        context = this
    )
)
.build()

The UI Layer composes the SDK Layer’s security wiring, but server-side prerequisites must already be in place before the client-side call has any effect:

  • setDCRConfiguration / setDcr — without a configured template HAAPI client on the Curity Identity Server that references the main client via authenticate-client-by, the framework’s first registration call returns 401 or 404 and the flow fails before the user sees a screen.
  • setTokenBoundConfiguration / setDpopTokenBinding — when the server has issue-token-bound-authorization-code=true but the client omits token binding (on iOS, passing UnboundedTokenConfiguration or omitting the call; on Android, omitting setDpopTokenBinding), the token-exchange step returns invalid_dpop_proof. The mismatch is silent until the user has already authenticated; see the matrix in Token Binding.
  • setRiskAssessmentConfiguration / setContext — without a configured risk-assessment integration at the Curity profile level, the device-context fields are forwarded but never consumed, and the auth step that depends on the risk score either runs without a score or fails with a flow-level error.

Configure both sides together; the SDK-Layer pages linked in the table above carry the server-side prerequisites for each knob.

Storage#

Platform-only: iOS.

By default the framework persists the authenticated session — token and DPoP state — in the app-private keychain, and no configuration is required. Call setStorage(_:) to share that session with an App Extension through a keychain access group, or to inject a custom Storage. DCR data inherits this store unless you give DCRConfiguration a storage of its own.

// share the session with an app extension via a keychain access group
.setStorage(StorageBuilder.Keychain().accessGroup("group.example.shared").build())

// optionally set the data-protection level (default: .afterFirstUnlockThisDeviceOnly)
.setStorage(
    StorageBuilder.Keychain()
        .accessGroup("group.example.shared")
        .accessibility(.whenUnlockedThisDeviceOnly)
        .build()
)

Data-protection levels#

Both levels are this-device-only: stored secrets are device-bound and never migrate to another device through an encrypted backup or iCloud. Choose the one that matches when your app needs to reach the session:

LevelWhen to choose itLimitations
.afterFirstUnlockThisDeviceOnly (default)The default for session continuity. Use when the app — or its extension — needs to read the session in the background, or at any point after the device has been unlocked at least once since boot (for example, a silent token refresh while backgrounded).Not accessible before the first unlock after a reboot: a background launch immediately after restart, before any unlock, fails with the keychain-unavailable error.
.whenUnlockedThisDeviceOnlyA stricter posture for when you never need background or locked access — items are reachable only while the device is currently unlocked, which minimizes the accessibility window.Any keychain access while the device is locked fails — including background silent refresh, an extension running while locked, and push-triggered background work.

In both cases a locked or just-booted keychain surfaces a typed, inspectable error rather than an opaque failure, so it can be retried once the app is in the foreground — see Storage Errors.

Was this helpful?