Module HAAPI iOS Sdk Documentation

The Hypermedia Authentication API (HAAPI) SDK for the Curity Identity Server allows integration of this API into your applications for smarter, simpler login using native UI widgets. It will enable any login method supported by the Curity Identity Server and strictly follows the principle of REST. The SDK is meant to make the security aspects of consuming this API easier.

Requirements

How to get started

Swift Package Manager

With Swift Package Manager, add the following dependency to your Package.swift.

dependencies: [
    .package(url: "https://github.com/curityio/ios-idsvr-haapi-sdk-dist")
]

Cocoapods

With Cocoapods, add the following line to your Podfile.

pod 'IdsvrHaapiSdk'

Usage

Configuration

Start by creating a HaapiConfiguration for the client access to the server.

swift
let baseURL = URL(string: "https://localhost:8443")!
let haapiConfiguration = HaapiConfiguration(name: "foo-name",
                                            clientId: "foo-client-id",
                                            baseURL: baseURL,
                                            tokenEndpointURL: URL(string: "/oauth/token", 
                                                                	relativeTo: baseURL)!,
                                            authorizationEndpointURL: URL(string: "/oauth/authorize", 
                                                                          relativeTo: baseURL)!,
                                            appRedirect: "foo-app-redirect",
                                            httpHeadersProvider: nil,
                                            authorizationParametersProvider: nil)

Using the HaapiAccessorBuilder to instantiate HAAPI and OAuth managers - the recommended way (required when using DCR fallback)

HaapiAccessorBuilder allows obtaining the accessors to access HAAPI and OAuth from the current device, based on an initial static configuration and the device's capabilities.

The preferred access strategy is to obtain HAAPI access tokens using client attestation, i.e. the device’s key attestation capabilities. If attestation is not supported, or if the Curity Identity Server deems the attestation data as invalid, an optional fallback strategy based on Dynamic Client Registration can be used.

The DCR-based fallback uses templatized client registration: the client configured in HaapiConfiguration is used to register a dynamic client based on a template ID configured via setDCRConfiguration. The registration happens on the first time the fallback is used for a given template client ID. The resulting client data is stored on the device and considered by HaapiAccessorBuilder on subsequent runs.

The HaapiAccessor instances created by this class include:

When the DCR-based access is used, both HaapiManager and OAuthTokenManager use credentials different from what’s supplied in the initial configuration.

The recommended way to use HaapiAccessorBuilder is to create and configure a single instance and invoke build once before going through an authorization flow via HAAPI.

Note that HaapiManager internally uses a HaapiTokenManager to manage the necessary resources to enforce security and identity when the app communicates with the Curity Identity Server. Please refer to the IdsvrHaapiDriver documentation to know more about the underlying framework.

swift
let haapiConfiguration: HaapiConfiguration = ...
let dcrConfiguration: DCRConfiguration = ...

let haapiAccessor: HaapiAccessor = HaapiAccessorBuilder(haapiConfiguration: haapiConfiguration)
        .setDCRConfiguration(configuration: dcrConfiguration)
        .build()

// HAAPI flow
let haapiManager: HaapiManager = haapiAccessor.haapiManager
haapiManager.start()
// ... haapiManager.submit/follow ...

Manually instantiating HaapiManager - use only if supporting only Attestation backed HAAPI flows

Instantiate HaapiManager with the HaapiConfiguration.

swift
var haapiManager: HaapiManager!
// ...
do {
    haapiManager = try HaapiManager(haapiConfiguration: haapiConfiguration)
} catch let haapiError as HaapiError {
    handleHaapiError(haapiError) // Check the section `Handling HaapiError`
} catch { // Error
		handleError(error)
}

Running the HAAPI flow (authorization flow)

Invoke HaapiManager.start to start the HAAPI flow that returns a HaapiResult.

The HaapiResult can return one of these cases:

The HaapiResult needs to be handled as illustrated below.

swift
haapiManager.start { [weak self] haapiResult in
    self?.handleHaapiResult(haapiResult)
}
//...

private func handleHaapiResult(_ haapiResult: HaapiResult) {
    switch haapiResult {
    case .representation(let haapiRepresentation): handleHaapiRepresentation(haapiRepresentation)
    case .problem(let problemRepresentation): handleProblemRepresentation(problemRepresentation)
    case .error(let anError): handleError(anError)
    }
}

private func handleHaapiRepresentation(_ haapiRepresentation: HaapiRepresentation) {
    switch haapiRepresentation {
    case let redirectionStep as RedirectionStep:
        print(redirectionStep)
    case let authenticatorSelectorStep as AuthenticatorSelectorStep: print(authenticatorSelectorStep)
    case let interactiveFormStep as InteractiveFormStep: print(interactiveFormStep)
    case let pollingStep as PollingStep: print(pollingStep)
    case let continueSameStep as ContinueSameStep: print(continueSameStep)
    case let userConsentStep as UserConsentStep: print(userConsentStep)
    case let genericRepresentationStep as GenericRepresentationStep: print(genericRepresentationStep)
    case let clientOperationStep as ClientOperationStep: print(clientOperationStep)
    case let oAuthAuthorizationResponseStep as OAuthAuthorizationResponseStep: print(oAuthAuthorizationResponseStep)
    default:
        print(haapiRepresentation)
    }
}

With a HaapiRepresentation, invoke HaapiManager.submit or HaapiManager.followLink to move forward on the HAAPI flow and handle the new HaapiResult.

swift
private func submit(formActionModel: FormActionModel, parameters: [String: Any]) {
    haapiManager.submitForm(formActionModel, parameters: parameters) { [weak self] haapiResult in
        self?.handleHaapiResult(haapiResult)
    }
}

private func followLink(_ link: Link) {
    haapiManager.followLink(link) { [weak self] haapiResult in
        self?.handleHaapiResult(haapiResult)
    }
}

When obtaining an OAuthAuthorizationResponseStep, the HAAPI flow reaches the end. OAuthAuthorizationResponseStep contains a code that is required to get an access_token.

OAuthTokenManager

Using the HaapiAccessorBuilder helper to instantiate Haapi and OAuth managers - the recommended way (mandatory when using DCR fallback)

The recommended way to use HaapiAccessorBuilder is to create and configure a single instance and invoke create once before going through an OAuth operation.

swift
let haapiConfiguration: HaapiConfiguration = ...
let dcrConfiguration: DCRConfiguration = ...

let oauthAccessor: OAuthAccessor = HaapiAccessorBuilder(haapiConfiguration: haapiConfiguration)
        .setHaapiAccessorOption(option: .oauth)
        .setDCRConfiguration(configuration: dcrConfiguration)
        .build()

// Fetch token with OAuthTokenManager
let oAuthTokenManager: OAuthTokenManager = oauthAccessor.oAuthTokenManager
// ... oAuthTokenManager.fetch/refresh ...

Manually instantiating OAuthTokenManager - use only if supporting only Attestation backed HAAPI flows

Instantiate an OAuthTokenManager with the HaapiConfiguration

Running the OAuth flow

Invoke OAuthTokenManager.fetchAccessToken with the code in OAuthAuthorizationResponseStep.oauthAuthorizationResponseProperties to get a TokenResponse.

When receiving a TokenResponse, the object can be:

swift
let code = authResponseStep.oauthAuthorizationResponseProperties.code!
let oauthTokenManager = OAuthTokenManager(oauthTokenConfiguration: haapiConfiguration)
oauthTokenManager.fetchAccessToken(with: code) { [weak self] tokenResponse in
    self?.handleTokenResponse(tokenResponse)
}
// ...

private func handleTokenResponse(_ tokenResponse: TokenResponse) {
    switch tokenResponse {
    case let .successfulToken(successfulToken):
        successfulToken.accessToken // access_token
        successfulToken.refreshToken // refresh_token
    case let .errorToken(errorTokenResponse):
        errorTokenResponse.error // error as a String
        errorTokenResponse.errorDescription // error description as a String
    case let .error(anError): handleError(anError)
    }
}

ℹ️ It is recommended to keep the SuccessfulToken especially the access_token and the refresh_token in a secure storage such as the Keychain.

If the access_token is expired, invoke OAuthTokenManager.refreshAccessToken to get a new TokenResponse.

swift
oauthTokenManager.refreshAccessToken(with: "refresh_token") { [weak self] tokenResponse in
    self?.handleTokenResponse(tokenResponse)
}

ℹ️ Check this for a concrete example.

Configurations options

Additional configurations/usages

Binding authorization code

Issue token-bound authorization code

When using issue-token-bound-authorization-code (true) in the Identity Server configuration, it is mandatory to bind the tokens on the client side.

To bind tokens on the client side, HaapiManager.dpop has to be provided to OAuthTokenManager when requesting the access_token as demonstrated below:

swift
let code = authResponseStep.oauthAuthorizationResponseProperties.code!
let dPoP = haapiManager.dpop
let oauthTokenManager = OAuthTokenManager(oauthTokenConfiguration: haapiConfiguration)

oauthTokenManager.fetchAccessToken(with: code, dpop: dPoP) { [weak self] tokenResponse in
    self?.handleTokenResponse(tokenResponse)
}

⚠️ Omitting the DPoP object when invoking OAuthTokenManager.fetchAccessToken results in a server error:

json
{
    "error": "invalid_dpop_proof",
    "error_description": "Missing DPoP Proof Token"
}

In that case, retry by invoking OAuthTokenManager.fetchAccessToken with the dpop parameter set as illustrated in the example above

Issue unbounded authorization code

On the other hand, when issue-token-bound-authorization-code is set to false in the Identity Server configuration, binding the tokens is a choice on the client side, .

To not bind the token on the client side, provide only the code when requesting the access_token via OAuthTokenManager as demonstrated below:

swift
let code = authResponseStep.oauthAuthorizationResponseProperties.code!
let oauthTokenManager = OAuthTokenManager(oauthTokenConfiguration: haapiConfiguration)

oauthTokenManager.fetchAccessToken(with: code) { [weak self] tokenResponse in
    self?.handleTokenResponse(tokenResponse)
}

Valid token binding configurations

The following table exposes all correct combinations:

Server configuration
issue-token-bound-authorization-code
Client configuration
TokenBoundConfiguration
Pass DPoP to OAuthTokenManager.fetchAccessToken Consequences
false UnboundedTokenConfiguration No The refresh_token is not bounded to the DPoP.
false Is_NOT_UnboundedTokenConfiguration[^1] Yes The refresh_token is bound to the DPoP.
true Is_NOT_UnboundedTokenConfiguration Yes The refresh_token is bound to the DPoP.
true Is_NOT_UnboundedTokenConfiguration No Server error: { "error": "invalid_dpop_proof", "error_description": "Missing DPoP Proof Token"}

[^1]: Client is configured with BoundedTokenConfiguration or the default configuration.

DPoP-Nonce / Listening to the response from the token endpoint via OAuthTokenManager

When invoking OAuthTokenManager.fetchAccessToken or OAuthTokenManager.refreshAccessToken, responses are handled and returned as TokenResponse. The TokenResponse may contain a SuccessfulTokenResponse, ErrorTokenResponse, or Error.

SuccessfulTokenResponse and ErrorTokenResponse contain the attributes as defined in RFC 6749.

With these objects, it is not possible to get the headers or the raw data response such as a dpop-nonce. If they are needed, a listener (OAuthTokenManager.TokenEndpointResponseListener) has to be configured.

The following snippet illustrates how to configure the listener.

swift
// Create a class that conforms to OAuthTokenManager.TokenEndpointResponseListener
@available(iOS 14.0, *)
class MyTokenEndpointResponseListener: OAuthTokenManager.TokenEndpointResponseListener {
    func onSuccess(_ value: OAuthTokenManager.SuccessTokenHTTPURLResponseContent) {
        // SuccessfulTokenResponse
        value.successfulTokenResponse.refreshToken
        value.successfulTokenResponse.accessToken
        value.successfulTokenResponse.expiresIn
        // DataAsMap
        if let accessToken = value.dataAsDictionary["access_token"] as? String {
            // ...
        }
        if let refreshToken = value.dataAsDictionary["refresh_token"] as? String {
            // ...
        }
        // HTTPURLResponse
        if let httpURLResponse = value.httpURLResponse {
            // Headers
            if let contentType = httpURLResponse.allHeaderFields["Content-Type"] as? String {
                // ...
            }
        }
      	// DPOP_NONCE
        value.headerFields["dpop-nonce"]
        // Error
        assert(value.error == nil)
    }
  
  	func onError(_ value: ErrorHTTPURLResponseContent) {
        // ERROR
        value.error.localizedDescription
        // DataAsMap
        if let errorDescription = value.dataAsDictionary["error_description"] as? String {
            // ...
        }
        if let error = value.dataAsDictionary["error"] as? String {
            // ...
        }
        // HTTPURLResponse
        if let httpURLResponse = value.httpURLResponse {
            // Headers
            if let contentType = httpURLResponse.allHeaderFields["Content-Type"] as? String {
                // ...
            }
        }

        errorContent = value
    }

    func onTokenError(_ value: OAuthTokenManager.ErrorTokenHTTPURLResponseContent) {
        // ErrorTokenResponse.
        value.errorTokenResponse.error
        value.errorTokenResponse.errorDescription
        // DataAsMap
        if let errorDescription = value.dataAsDictionary["error_description"] as? String {
            // ...
        }
        if let error = value.dataAsDictionary["error"] as? String {
            // ...
        }
        // HTTPURLResponse
        if let httpURLResponse = value.httpURLResponse {
            // Headers
            if let contentType = httpURLResponse.allHeaderFields["Content-Type"] as? String {
                // ...
            }
        }
    }
}

// Create a HaapiConfiguration with MyTokenEndpointResponseListener
let haapiConfiguration = HaapiConfiguration(name: "foo-name",
                                            ...
                                            tokenEndpointResponseListener: MyTokenEndpointResponseListener())
// Create an instance of OAuthTokenManager with the configuration
OAuthTokenManager(oauthTokenConfiguration: haapiConfiguration)

The listener is triggered when receiving a response from the token endpoint: OAuthTokenManager.SuccessTokenHTTPURLResponseContent, OAuthTokenManager.ErrorTokenHTTPURLResponseContent, or ErrorHTTPURLResponseContent.

When opting for this configuration, the TokenResponse can be ignored as HTTPURLResponseContent contains the raw response.

When using risk assessment services (ex: BankID's risk assessment functionality)

To enable the collection of necessary information about the device for the risk assessment service, setting the application bundle is required, as demonstrated below.

swift
let haapiConfiguration = HaapiConfiguration(name: "foo-name",
                                            ...
                                            applicationBundle: Bundle.main)

It allows the framework to collect and manage the necessary information to provide the service.

For reference about the collected information please refer to the official BankID Relying Party Guidelines for version 6, and API documentation.

⚠️ Server support for the risk assessment functionality integration requires a version of the Curity Identity Server starting from 9.7.0.

Client Authentication Method

Using Attestation to enforce API security should be the default behavior, which should work for most users. For the signing key-based client attestation method to work, there must be hardware support, and this can sometimes be uncertain due to the multitude of different device models and user setup in day to day use.

Non-compliant devices provide their alternative proof via the Client Credentials Flow, in a request for an access token with the dcr scope. There are multiple ways in which the credential can be supplied during this request.

Secret

A simple client secret can be used to request the client credentials, which will need to be the same for all users. This is not a secure option, but it can be useful in some setups, such as when first getting integrated, or as a solution for a development stage of the deployment pipeline.

swift
let clientAuthenticationMethod = ClientAuthenticationMethodSecret("foo")

Certificate

The app can use a client certificate bundled with it or import it from its public key hash representation. Then, it sends the proof of ownership to the Curity Identity Server over a Mutual TLS connection. The Curity Identity Server is configured to verify the trust chain of the client certificate.

swift
// using bundled certificates
let clientAuthenticationMethod = try ClientAuthenticationMethodMTLS(pkcs12Filename: pkcs12Filename, 
                                                                    pkcs12Passphrase: passphrase, 
                                                                    serverPEMFilename: serverPEMFilename, 
                                                                    isValidatingHostname: true, 
                                                                    bundle: appBundle))

// using server public key hash
let mtlsClientAuth2 = try! ClientAuthenticationMethodMTLS(pkcs12Filename: pkcs12Filename, 
                                                          pkcs12Passphrase: passphrase, 
                                                          serverKeyPinnings: [KeyPinning(hostname: "192.168.1.107", 
                                                                                         publicKeyHash: "Kjuy4mT3fbeDozRNP6rTjWRYmbs79Begb5Roq+DUu7s=")],
                                                          bundle: appBundle)

Client Assertion

The app can make use of both symmetric and asymmetric keypairs and load them into the device Keychain, then use it to produce a JWT Client Assertion, which it then sends as proof of ownership. The Curity Identity Server is then configured with a way to get the public key with which to verify received assertions.

swift
// Asymmetric keypair
let asymConfig = try ClientAuthenticationMethodJWTAsymmetric(pemFilename: rsaPrivateKeyPEM, signatureAlgorithm: .rs256, bundle: appBundle)

// Symmetric keypair
let secretKeyJWT = "_UQ49FAPrM0XOdjNROeVIrbfPvTzqVgfLBi66Nk0mIBttc9ZBakcC9ZiuAu9WCvg"
let symConfig = ClientAuthenticationMethodJWTSymmetric(signatureAlgorithm: .hs256, secretKey: secretKeyJWT)

DCR - Dynamic Client Registration

Instances of an app running on non-compliant devices will have to prove their identity based on an alternative client credential and perform a Client Credential Flow, resulting in the creation of a dynamic client. Here you can find more information on how to setup the server environment and prepare the configuration.

swift
let dcrEndpointURL = URL(string: "/oauth/oauth-registration", relativeTo: serverBaseURL)
let dcrConfiguration = DCRConfiguration(templateClientId: "dcr-client-template-id", clientRegistrationEndpointUrl: dcrEndpointURL)

⚠️ When setting HaapiConfiguration.useAttestation value to false or when providing a DCRConfiguration it is required to set the ClientAuthenticationMethod to a value different than ClientAuthenticationMethodNone otherwise it will fail.

Error handling

IdsvrError

When using HaapiManager or OAuthTokenManager, their respective completion handler can return an Error. This error can be cast to an HaapiError as demonstrated below:

swift
private func handleError(_ error: Error) {
    guard let haapiError = error as? HaapiError else { return }
    switch haapiError {
    case .assertionFailure(let cause): cause
    case .attestationFailure(let cause): cause
    case .attestationKeyGenFailure(let cause): cause
    case .attestationNotSupported:
    case .attestationRefusedByServer(let cause): cause
    case .communication(let message, let cause): cause
    case .dpopKeyCreationFailure(let cause): cause
    case .dpopProofCreationFailure(let cause): cause
    case .dpopProofFailure(let message, let cause):
      message
      cause
    case .haapiTokenManagerAlreadyExists(let name): name
    case .haapiTokenManagerIsClosed:
    case .haapiTokenManagerIsExpired:
    case .illegalState(let message): message
    case .invalidConfiguration(let reason): reason
    case .invalidStatusCode(let statusCode): statusCode
    case .serverError(let error, let errorDescription, let statusCode):
      error
      errorDescription
      statusCode
    }
}

An HaapiError is conforming to IdsvrError that provides messages describing why an error occurred and provides more information about the error. It is possible to try to cast an Error to IdsvrError and get the following information:

swift
private func handleHaapiError(_ error: HaapiError) {
        guard let haapiError = error as IdsvrError else { return }
        
        // check the error metadata for information about the error.
        // for example, check the recovery suggestion to find out if the error can be recovered
        
        switch haapiError.recoverySuggestion {
        case .retryable(condition: let condition):
            // The error can be recovered by retrying the same operation when the `condition` is met.
            // For example, retrying the same operation at a later time, when the app is running in the foreground.
            // This is likely to happen when the device's resources are currently unavailable
        case .newHaapiFlow:
            // The error can be recovered by triggering a new Authentication flow from the start.
        case .nonRecoverable(action: let action):
            // This error cannot be recovered without compile time or runtime corrective measures as instructed by the `action`.
        }
    }

Common errors

HaapiError.invalidConfiguration in OAuthTokenManager

💥 This error happens when using UnboundedTokenConfiguration and providing a DPoP object to OAuthTokenManager.fetchAccessToken() .

🛠 Align the client configuration and the framework API usage as explained [here](#Issue token-bound authorization code).

Dpop proof related errors - HaapiError.dpopKeyCreationFailure, HaapiError.dpopProofCreationFailure and HaapiError.dpopProofFailure

💥 When receiving an HaapiError.dpopKeyCreationFailure it is recommended to trigger a retry at a later time, if possible, after the device has been rebooted as it provides the better chances of recovering gracefully. If the error is persistent, it means the device cannot fulfill the requirements for the current client configuration settings and a different token binding configuration is required.

💥 When fetching the access_token and implementing dpop token binding, errors may occur (e.g. while interacting with the Secure Enclave API or the secure storage) when invoking OAuthTokenManager.refreshToken preventing the DPoP proof mechanism from functioning correctly. In such cases, the client developer code will receive a HaapiError.dpopProofCreationFailure or a HaapiError.dpopProofFailure to allow the app to react to such errors. More information about the origin of the error can be found in the underlying cause value.

When the error is persistent, the app can switch the token binding configuration and not use the Secure Enclave by providing a different keyPairType parameter such as the P256 NIST algorithm keypair.

❗️ If HaapiError.dpopXXXFailure is persistent when invoking OAuthTokenManager.refreshToken, the reasons may be caused by:

🛠 It is recommended to trigger the retry when the application is in the foreground for better chances of recovering gracefully.

If the error is still happening in the foreground, the application might be required to switch the client configuration BoundedTokenConfiguration to use different settings.

Switching configuration involves:

Below is a snippet of code suggestion on how to handle the HaapiError.dpopProofFailure and switch to a fallback configuration

swift
let secureEnclaveHaapiConfiguration = HaapiConfiguration(name: CONFIG_ALIAS,
                                                        ...)
let tokenManager = OAuthTokenManager(oauthTokenConfiguration: secureEnclaveHaapiConfiguration)
tokenManager.refreshAccessToken(with: token) { tokenResponse in
    if case let .error(cause) = tokenResponse, case let HaapiError.dpopProofFailure(message, error) = cause {
        // Error is from the dpop binding mechanism
        // message and error can be inspected for details about the problem
        
        // Suggested handling:
        // Check the application state:
        //  - if is `background` ignore the error and backoff as subsequent calls in this code execution path are likely to also fail
        //  - else implement a retry mechanism
        //      - store some marker/counter that provides info whether is the first time the refresh is called or a retry
        //      - based on the value of the marker/counter infer if the error is persistent
        //         - if the error is persistent, trigger a new authentication flow with different configuration
        return
    }
    
    // handle other OAuthCompletion
}

// when the persistent error conditions are met use the fallback configuration to start a new authentication
let fallbackTokenBoundConfiguration = BoundedTokenConfiguration(keyPairType: CryptoKeyType.p256)
let fallbackHaapiConfiguration = HaapiConfiguration(name: CONFIG_ALIAS,
                                            				...
                                            				tokenBoundConfiguration: fallbackTokenBoundConfiguration)
                                            
let haapiManager = HaapiManager(haapiConfiguration: fallbackHaapiConfiguration)
haapiManager.start(...)

Error Domain=com.apple.devicecheck.error Code=3

💥 This error happens when using DCAppAttestService and asking Apple to attest the validity of a generated cryptographic key. To our knowledge, very few users are impacted by this problem. It can be linked to jailbroken devices, to Apple risk score for the user or device, corporate devices (MDM), and unknown cases not detailed in the Apple documentation.

🛠 Unfortunately, the cause is unknown and the Apple documentation does not explain the reason for the failure. Devices that encounter this problem cannot execute the HAAPI flow and therefore, they cannot get an access_token. Please contact Curity Support to know how to handle devices that cannot support attestation on iOS.

HaapiLogger

When using IdsvrHaapiSdk, it is possible to display the logs in the console as demonstrated below.

swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  // ...
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // ...
      	HaapiLogger.followUpTags = DriverFollowUpTag.allCases + SdkFollowUpTag.allCases // To filter logs.
        return true
    }
  // ...
}

Display logs by configuring HaapiLogger.followUpTags

swift
// Driver and Sdk logs are displayed in the console
HaapiLogger.followUpTags = DriverFollowUpTag.allCases + SdkFollowUpTag.allCases
// Only SDK logs are displayed in the console.
HaapiLogger.followUpTags = SdkFollowUpTag.allCases

⚠️ If HaapiLogger.followUpTags is not set, then no logs are displayed in the console.

Supported log levels

HaapiLogger only uses the following log levels:

Log level How to enable Default value
Debug HaapiLogger.isDebugEnabled It is set to true in DEBUG mode. Otherwise, it is set to false.
Info HaapiLogger.isInfoEnabled It is set to true.
Warning HaapiLogger.isWarningEnabled It is set to true.
Error HaapiLogger.isErrorEnabled It is set to true.

Display masking sensitive data

By default, HaapiLogger.isSensitiveValueMasked is set to true. With this configuration, logs are displayed with masking value such as:

2023-08-08 22:03:13.866367+0200 IdsvrHaapiUIKitApp[19098:404410] [HAAPI_DRIVER_HTTP] [DEBUG] HaapiTokenManager.378 : Requesting challenge from *****/cat

To remove the masking, HaapiLogger.isSensitiveValueMasked has to be set to false. Now with this configuration, logs are displayed without masking value but followed with warnings logs such as:

2023-08-08 22:06:18.982975+0200 IdsvrHaapiUIKitApp[19269:408868] [UNSECURED] ********** SENSITIVE VALUE IS UNMASKED **********
2023-08-08 22:06:18.983071+0200 IdsvrHaapiUIKitApp[19269:408868] [UNSECURED] ********** HaapiLogger.isSensitiveValueMasked must be set to true in `release` mode. **********
2023-08-08 22:06:18.983182+0200 IdsvrHaapiUIKitApp[19269:408868] [HAAPI_DRIVER_HTTP] [DEBUG] HaapiTokenManager.503 : Requesting CAT from https://localhost:8443/dev/oauth/token/cat

❗️Setting this value to false is only recommended when debugging.

Read the logs

All logs are structured like the following:

2023-06-29 16:31:50.845676+0200 IdsvrHaapiUIKitApp[66219:26995872] [HAAPI_UI_THEMING] [DEBUG] UIStylesContainer.34 : Loading styles definitions from IdsvrHaapiUIKit

It is possible to filter the Haapi logs by using the prefix: HAAPI.

To filter Driver logs, use the prefix: HAAPI_DRIVER. Here are the following up tags for the driver:

To filter Sdk logs, use the prefix: HAAPI_SDK. Here are the following up tags for the sdk:

Write logs to another destination

When using IdsvrHaapiDriver, it is possible to write the logs to another destination as demonstrated below.

swift
class MyLogSink: LogSink {
    func writeLog(logType: LogType,
                followUpTag: any FollowUpTag,
                message: String,
                file: String,
                line: Int)
    {
        // Filter/export to your designed tool
    }
}

HaapiLogger.appendLogSink(MyLogSink())

Reference Documentation

Protocols

Structs

Classes

Enums

Extensions

Typealiases