Module HAAPI iOS UIKit Documentation
An iOS XCFramework with classes and layout definitions that are used to perform an Haapi Flow
authorization with Curity Identity Server Hypermedia Authentication API (HAAPI) from iOS devices.
The framework allows a client to implement an authorization flow via HAAPI by using a set of prebuilt UI screens that represent the different responses/steps that may be involved in the API-driven flow.
Usage examples
UIKit application
Start by creating the configurations for the client access to the server and user flow (HaapiUIKitConfiguration), and create an HaapiUIKitApplication
in your AppDelegate.
@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 {
// Override point for customization after application launch.
haapiUIKitApplication = HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration).build()
return true
}
// ...
}
Start the Haapi flow in your main UIViewController that is implementing HaapiFlowResult
to receive a result at the end of the Haapi flow or an error during the flow.
extension UIApplication {
var haapiUIKitApplication: HaapiUIKitApplication {
get {
guard let appDelegate = delegate as? AppDelegate else { fatalError("...") }
return appDelegate.haapiUIKitApplication
}
}
}
// ... Presented UIViewController
do {
try HaapiFlow.start(from: self, // self is an UIViewController that is presented.
haapiUIKitApplication: UIApplication.shared.haapiUIKitApplication,
haapiDeepLinkManageable: HaapiDeepLinkManager.shared)
} catch {
// handle error if the HaapiFlow does not start.
}
// ...
SwiftUI application
Start by creating the configurations for the client access to the server and user flow (HaapiUIKitConfiguration) in an AppDelegate.
class AppDelegate: NSObject, 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 {
// Override point for customization after application launch.
haapiUIKitApplication = HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration).build()
return true
}
}
Hook the AppDelegate with the struct App
.
@main
struct SwiftUIApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView(haapiApplication: appDelegate.haapiUIKitApplication)
}
}
// ...
}
Implement HaapiFlowResult
in the View
that can start the Haapi flow and start the Haapi flow. HaapiFlowResult
provides a set of methods to receive a result at the end of the Haapi flow or an error during the flow
struct ContentView: View, HaapiFlowResult {
let haapiApplication: HaapiUIKitApplication
@State private var showingHaapiVC = false
var body: some View {
VStack {
Button {
showingHaapiVC = true
} label: {
Text("Start Haapi flow")
}
}
.sheet(isPresented: $showingHaapiVC) {
HaapiFlow.start(self,
haapiUIKitApplication: haapiApplication,
haapiDeepLinkManageable: HaapiDeepLinkManager.shared)
}
// ...
}
func didReceiveOAuthTokenModel(_ oAuthTokenModel: IdsvrHaapiUIKit.OAuthTokenModel) { /* ... */ }
func didReceiveError(_ error: Error) { /* ... */ }
}
HaapiUIKitConfiguration options
As demonstrated in [Usage examples
](#Usage examples), HaapiUIKitConfiguration
is being used to build an HaapiUIKitApplication
via HaapiUIKitApplicationBuilder
. When using HaapiUIKitConfiguration, it is possible to configure a few options:
- autoPollingDuration
- shouldAutoHandleFlowErrorFeedback
- presentationMode
- authenticationSelectionPresentation
- shouldConfirmFlowInterruption
- useDefaultExternalBrowser
autoPollingDuration
During the Haapi Flow
, when a polling process is invoked then an automatic polling waits a few seconds between triggering polling requests to the server. The default value is 3 seconds. It is possible to configure a number of seconds for the automatic polling. When the value is greater than 0 then the Haapi Flow
will automatically handle the polling process, else it will disable automatic polling behavior.
shouldAutoHandleFlowErrorFeedback
It is possible that during the Haapi Flow
, the server returns an unexpected problem or an internal exception occurs on the framework which prevents the flow from proceeding and completing. In this case, as a good practice, the user is notified when something bad happened, thus preventing them from completing an action.
When set to true
, an AlertDialog is presented and gives the user some feedback about the error which user interaction causes the flow to finish and to return the error information to HaapiFlowResult
.
When set to false
, the flow is directly interrupted and returns the error information to HaapiFlowResult which delegates the user feedback responsibility to the application developer.
presentationMode
This configuration informs HaapiFlow.start()
how HaapiFlowViewController
is presented. The presentation determines the type of animation and transition when presenting HaapiFlowViewController to the client's application. The default value is set to PresentationMode.modal
. PresentationMode.stack
is another option.
With Presentation.modal
, HaapiFlowViewController is presented as a modal" navigation, presenting the flow view controller entering from the bottom to the top and closing with an animation from top to bottom.
With Presentation.stack
, HaapiFlowViewController is presented as a stack navigation, presenting the flow view controller entering from right to left and closing with an animation from left to right.
authenticationSelectionPresentation
This configuration informs how HaapiFlowViewController presents the AuthenticatorSelectorStep
. The presentation determines how the elements of an AuthenticatorSelectorStep
are presented. The default value is set to AuthenticatorSelectionPresentation.list
. AuthenticatorSelectionPresentation.tabs
is another option.
With AuthenticatorSelectionPresentation.list
, authenticators are rendered as a scrollable list.
With AuthenticatorSelectionPresentation.tabs
, authenticators are rendered with a TabbedPager
. A Tab element displays the authenticators from left to right and a "Pager" displays the selected authenticator's content.
shouldConfirmFlowInterruption
When pressing the back button or the close button during the Haapi Flow
, it is possible to display an alert that informs the user that the flow is going to be interrupted. The default value is true
. When the value is set to false
then no alert is displayed when pressing the back button or the close button.
useDefaultExternalBrowser
When the app needs to trigger some kind of external interaction action, it does so by delegating the action URL to be handled by the OS, either by using an ASWebAuthenticationSession
which embeds the browser in the app, or by using the default external browser. The default value for this configuration is false
which instructs the use of ASWebAuthenticationSession
.
HaapiFlowViewModel
The HaapiFlowViewModel
is the main driver for the execution and stepping through the authentication flow and it is used internally by the framework to mediate the interaction with the Curity Identity Server. However it is also possible to use it as a standalone functional component allowing a client developer to build a completely custom UI and use the viewmodel as the internal driver to their authentication flow.
To do so, there's a public construtor available and then it is possible to subscribe to state changes on the @Published
state variables.
var haapiUIKitConfiguration = HaapiUIKitConfigurationBuilder(...).build()
...
var haapiUIKitApplication = HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration).build()
...
let haapiFlowViewModel = HaapiFlowViewModel(haapiUIKitApplication: haapiUIKitApplication)
haapiFlowViewModel.$isLoading
.receive(on: DispatchQueue.main)
.sink(receiveValue: { isLoading in
// handle isLoading
})
.store(in: &cancellables)
haapiFlowViewModel.$uiModel
.receive(on: DispatchQueue.main)
.sink(receiveValue: { result in
// handle uiModel change
})
.store(in: &cancellables)
haapiFlowViewModel.$error
.receive(on: DispatchQueue.main)
.sink(receiveValue: { error in
// handle error
})
.store(in: &cancellables)
haapiFlowViewModel.$oAuthModel
.receive(on: DispatchQueue.main)
.sink(receiveValue: { oAuthModel in
// handle OAuthModel
}
.store(in: &cancellables)
Starting the Haapi Authentication flow
The framework provides some convenience helper methods to setup and start the authentication flow inside the application.
To do so, an HaapiFlow
API is provided and the user is able to easily trigger the presentation of the flow by using it like below.
HaapiFlow.start(from: parentVC,
haapiUIKitApplication: haapiApplication,
haapiDeepLinkManageable: HaapiDeepLinkManager.shared)
When the configuration for the presentation mode is set to STACK
, using the convenience flow helpers from SwiftUI code OR when running the app in iOS version below 16.0, a back navigation button is placed with a label that can be customized by providing a translation for the key hui_back
and the tint for the button text and image can be customized by providing the theme's HaapiFlowViewControler
style color configuration with the key closeButtonTintColorName
.
Alternatively, if more control over the presentation and lifecycle management is necessary, when using from UIKit code an instance of HaapiFlowViewController
can be manually created. If using SwiftUI, then a HaapiFlowViewControllerRepresentable
is available or the developer can write their own custom implementation of UIViewControllerRepresentable
wrapping the HaapiFlowViewController
.
⚠️ Important:
Always make sure to thoroughly test the app's authentication flow navigation.
On iOS below 16.0, running from SwiftUI to present the flow in a
NavigationView
, using aNavigationLink
and set it'sdestination
to callHaapiFlow.start()
may cause unexpected behavior due to the destinationView
being immediately initialized when the link is rendered and it's state internally kept until used when navigating. To avoid this behaviour it is possible and recomended to use the modifier flagisActive
and bind it to a state variable that will trigger the init only when set to true, like the example below by clicking a button that triggers the NavigationLink. Also, make sure to setnavigationViewStyle
to.stack
to avoid unexpected behaviour.
NavigationView {
NavigationLink(destination: HaapiFlow.start(self,
haapiUIKitApplication: haapiApplication,
haapiDeepLinkManageable: HaapiDeepLinkManager.shared),
isActive: $isShowingStackVC) { EmptyView() }
Button("Start Haapi Stack") {
self.isShowingStackVC = true
}
}.navigationViewStyle(.stack)
Starting from iOS 16.0, it is recomended to replace
NavigationView
withNavigationStack
due to API deprecation. The example below demonstrates a fleshed out version of the changes. More information onNavigationView/NavigationStack
migration here.
@State private var path: [NavigationPath] = []
NavigationStack {
NavigationLink("Start Haapi Stack", value: NavigationPath.haapiVC)
.navigationDestination(for: NavigationPath.self) { navigation in
switch navigation {
case .haapiVC:
HaapiFlow.start(self,
haapiUIKitApplication: haapiApplication,
haapiDeepLinkManageable: HaapiDeepLinkManager.shared)
}
}
}
Theming
When using IdsvrHaapiUIKit in the application, UI components including ViewControllers are themed using colors, images, font, and UI configurations specified in a plist file. Everything that is displayed when performing an Haapi flow can be themed to some extent.
Each UI component uses its own style definition. A style definition contains keys and values. The latest are String, Number, Boolean, or Dictionary. When trying to change any aspects of the UI component as demonstrated below, it is important to fit the specification. Otherwise, the application may crash and if it is the case, please check the logs in the console.
How to override colors that are used in IdsvrHaapiUIKit?
In the application Asset catalog, define an existing key and provide a new color. Here is the list of colors that are being used.
For example, to change hui_background
, declare the same key in the application Asset catalog and define the colors. When an UIComponent in IdsvrHaapiUIKit uses hui_background
, it uses the ones declared in the application Asset catalog instead of the framework.
It is also possible to assign for the UIComponent in IdsvrHaapiUIKit, via a custom theme, to use a color that is not in the list. See below how to assign a custom theme.
How to override images that are used in IdsvrHaapiUIKit?
In the application Asset catalog, define an existing key and provide a new image. Here is the list of images that are being used.
For example, to change hui_ic_authenticator_user
, declare the same key in the application Asset catalog and provide the image asset. When an UIComponent in IdsvrHaapiUIKit uses hui_ic_authenticator_user
, it uses the ones declared in the application Asset catalog instead of the framework.
How to change the font in IdsvrHaapiUIKit?
IdsvrHaapiUIKit uses by default Roboto
font.
If the current font does not meet your needs, it is possible to change it. There are 2 ways: existing font from the iOS system or custom font.
Using an existing font from the iOS system
In the iOS system, here is the list of supported fonts.
For example, to change the font to Baskerville
, create a plist
file in the application. In the plist file, add a new key of String named fontName
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>fontName</key>
<string>Baskerville</string>
</dict>
</plist>
When using HaapiUIKitApplicationBuilder
, the plist file name needs to be provided. For example, if the plist file is named: "MyCustom.plist
". As explained above in the AppDelegate when using HaapiUIKitApplicationBuilder
, a new method needs to be invoked.
HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration)
.setThemingPlistFileName("MyCustom") // It referes to MyCustom.plist.
.build()
Using a custom font
To use a custom font, here are the requirements:
- Add the files related to the custom font to the application workspace. The files need to be a
ttf
file extension and target the application workspace. - Create a
plist
file in the application. In the plist file, add a new key of String namedfontName
. The value is the font family name using title case representation. For example, if the files were added and have the prefix,IBMPlexMono
, then the font family name title case representation isIBM Plex Mono
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>fontName</key>
<string>IBM Plex Mono</string>
</dict>
</plist>
When using HaapiUIKitApplicationBuilder
, the plist file name needs to be provided. For example, if the plist file is named: "MyCustom.plist
". As explained above in the AppDelegate when using HaapiUIKitApplicationBuilder
, a new method needs to be invoked.
HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration)
.setThemingPlistFileName("MyCustom") // It referes to MyCustom.plist.
.build()
When starting the application with these changes, the logs should indicate if the custom font was added with success.
2023-08-10 21:32:18.512355+0200 IdsvrHaapiUIKitApp[36182:2904595] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.291 : There are initially 84 registered fonts.
2023-08-10 21:32:18.528310+0200 IdsvrHaapiUIKitApp[36182:2904595] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.305 : Now there are 86 registered fonts.
2023-08-10 21:32:18.529603+0200 IdsvrHaapiUIKitApp[36182:2904595] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.308 : These fonts were added: ["Roboto", "IBM Plex Mono"])
As shown above, the custom font "IBM Plex Mono" was added. (Roboto is the default font that is used in the framework.)
ℹ️ If the fontName
was configured with IBMPlexMono
instead of IBM Plex Mono
. A warning will be displayed when the application starts as shown below. ⚠️ If the name is configured incorrectly, then the application crashes.
2023-08-10 21:50:51.418855+0200 IdsvrHaapiUIKitApp[37474:2932255] [HAAPI_UI_THEMING] [WARNING] ThemeRegistry.316 : The configured fontName is `IBMPlexMono`. Isn't it `IBM Plex Mono`?
⚠️ If the fonts were already registered in the application's project by declaring them in the application's info plist file as the example below.
<dict>
<key>UIAppFonts</key>
<array>
<string>IBMPlexMono-Bold.ttf</string>
<string>IBMPlexMono-BoldItalic.ttf</string>
<string>IBMPlexMono-ExtraLight.ttf</string>
<string>IBMPlexMono-ExtraLightItalic.ttf</string>
<string>IBMPlexMono-Italic.ttf</string>
<string>IBMPlexMono-Light.ttf</string>
<string>IBMPlexMono-LightItalic.ttf</string>
<string>IBMPlexMono-Medium.ttf</string>
<string>IBMPlexMono-MediumItalic.ttf</string>
<string>IBMPlexMono-Regular.ttf</string>
<string>IBMPlexMono-SemiBold.ttf</string>
<string>IBMPlexMono-SemiBoldItalic.ttf</string>
<string>IBMPlexMono-Text.ttf</string>
<string>IBMPlexMono-TextItalic.ttf</string>
<string>IBMPlexMono-Thin.ttf</string>
<string>IBMPlexMono-ThinItalic.ttf</string>
</array>
</dict>
</plist>
The success log is different because ThemeRegistry
does not need to register the custom font.
2023-08-10 21:56:09.982900+0200 IdsvrHaapiUIKitApp[37856:2940070] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.291 : There are initially 86 registered fonts.
2023-08-10 21:56:09.995493+0200 IdsvrHaapiUIKitApp[37856:2940070] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.305 : Now there are 87 registered fonts.
2023-08-10 21:56:09.996721+0200 IdsvrHaapiUIKitApp[37856:2940070] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.308 : These fonts were added: ["Roboto"])
2023-08-10 21:56:09.998978+0200 IdsvrHaapiUIKitApp[37856:2940070] [HAAPI_UI_THEMING] [INFO] ThemeRegistry.94 : ThemeRegistry is setup.
How to identify which themable components are displayed?
The HaapiLogger
requires to be configured:
HaapiLogger.followUpTags = [UIKitFollowUpTag.theming, UIKitFollowUpTag.component, UIKitFollowUpTag.layout]
HaapiLogger.isDebugEnabled = true
When running the flow, check in the console for the follow up tag: "HAAPI_UI_LAYOUT". Here is an example:
2023-08-08 23:43:10.820923+0200 IdsvrHaapiUIKitApp[24669:526101] [HAAPI_UI_LAYOUT] [DEBUG] HaapiFlowViewController.246 : Presenting <IdsvrHaapiUIKit.HaapiFlowViewController: 0x7f8230023200> can be themed and uses these themable elements: ["NotificationBannerView", "LoadingIndicatorView", "LoadingIndicatorView.ButtonPrimary", "LoadingIndicatorView.ButtonSecondary", "LoadingIndicatorView.ButtonText", "LoadingIndicatorView.ButtonLinkView", "StackView", "StackView.Interactions", "StackView.Links", "StackView.Polling"] - it is not an exhaustive list.
How to change the UI configurations?
Every UI component that is used in IdsvrHaapiUIKit can be themed. The theming is relying on a set of configuration that reference the color, image or primitive values such as String, Number, Boolean, etc. The set of configuration can be overridden by providing a plist file that overrides any specific keys with the corresponding values.
⚠️ Providing an incorrect value to a valid key may crash the application. Please check carefully the specification for the corresponding style definition.
Here is the list of style definitions for every UI component.
Here are a few examples:
Changing UICTFontTextStyleBody
UICTFontTextStyleBody is a key that represents the font size. This key is being used by many UI components. In IdsvrHaapiUIKit, the value is 17
.
How to change to
15
?
Create or use an existing plist
file in the application. In the plist file, add a new key of String named fontName
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UICTFontTextStyleBody</key>
<number>15</number>
</dict>
</plist>
When using HaapiUIKitApplicationBuilder
, the plist file name needs to be provided. For example, if the plist file is named: "MyCustom.plist
". As explained above in the AppDelegate when using HaapiUIKitApplicationBuilder
, a new method needs to be invoked.
HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration)
.setThemingPlistFileName("MyCustom") // It referes to MyCustom.plist.
.build()
Now every UI component that uses UICTFontTextStyleBody is affected by this change.
Override TextAppearance.Body
TextAppearance.Body is a key that represents a text appearance that uses UICTFontTextStyleBody
and natural
text alignment (source). This key is used by some UI component such as ActionableButton.Primary.
How to change the text appearance (italic) for ActionableButton.Primary without affecting the other UI component?
Create or use an existing plist
file in the application. In the plist file, add a dictionary that fits the requirements for a TextAppearance
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>TextAppearance.MyCustomBody</key>
<dict>
<key>fontSymbolicTrait</key>
<string>traitItalic</string>
</dict>
</dict>
</plist>
In the same plist file, add a dictionary that fits the requirements for ActionableButton.Primary.
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>TextAppearance.MyCustomBody</key>
<dict>
<key>fontSymbolicTrait</key>
<string>traitItalic</string>
</dict>
<key>ActionableButton.Primary</key>
<dict>
<key>textAppearance</key>
<string>TextAppearance.Body</string>
</dict>
</dict>
</plist>
When using HaapiUIKitApplicationBuilder
, the plist file name needs to be provided. For example, if the plist file is named: "MyCustom.plist
". As explained above in the AppDelegate when using HaapiUIKitApplicationBuilder
, a new method needs to be invoked.
HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration)
.setThemingPlistFileName("MyCustom") // It referes to MyCustom.plist.
.build()
Now a component that uses ActionableButton.Primary style will only be affected by this change. The change only affects the textAppearance
. As highlighted above, there is no need to mention the other keys because the other keys/values are taken from the initial ActionableButton.Primary definition from IdsvrHaapiUIKit.
Define another color in a style definition
textColorName
is a key that exists in many style definitions. For example. this key is used in ActionableButton.Primary.
How to define a textColorName for ActionableButton.Primary ?
In the application Asset catalog, define a new set of colors that is named: "pastel_color".
Create or use an existing plist
file in the application. In the plist file, add a dictionary that fits the requirements for a TextAppearance
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ActionableButton.Primary</key>
<dict>
<key>textColorName</key>
<string>pastel_color</string>
</dict>
</dict>
</plist>
When using HaapiUIKitApplicationBuilder
, the plist file name needs to be provided. For example, if the plist file is named: "MyCustom.plist
". As explained above in the AppDelegate when using HaapiUIKitApplicationBuilder
, a new method needs to be invoked.
HaapiUIKitApplicationBuilder(haapiUIKitConfiguration: haapiUIKitConfiguration)
.setThemingPlistFileName("MyCustom") // It referes to MyCustom.plist.
.build()
Now a component that uses ActionableButton.Primary style will only be affected by this change. The change only affects the textColorName
. As highlighted above, there is no need to mention the other keys because the other keys/values are taken from the initial ActionableButton.Primary definition from IdsvrHaapiUIKit.
Reference documentation
- ActionableButton.LinkView
- ActionableButton.Primary
- ActionableButton.Secondary
- ActionableButton.Text
- CheckboxView
- Colors
- FormViewController
- GenericViewController
- HaapiFlowViewController
- HeaderView
- Images
- InputTextField
- InputTextField.Curity
- InputTextField.Filled
- InputTextField.Outlined
- LinkView
- LoadingIndicatorView
- LoadingIndicatorView.ButtonLinkView
- LoadingIndicatorView.ButtonPrimary
- LoadingIndicatorView.ButtonSecondary
- LoadingIndicatorView.ButtonText
- MessageView.Content
- MessageView.Error
- MessageView.Heading
- MessageView.Info
- MessageView.RecipientOfCommunication
- MessageView.Username
- MessageView.Warn
- NotificationBannerView
- PollingViewController
- ProblemViewController
- SelectorViewController
- StackView
- StackView.Interactions
- StackView.Links
- StackView.Polling
- TabView
- TextAppearance.Body
- TextAppearance.Body.Bold
- TextAppearance.BodySubHeadline
- TextAppearance.Callout
- TextAppearance.Callout.Bold
- TextAppearance.Content
- TextAppearance.Link
- TextAppearance.SubHeadline
- TextAppearance.Text
- TextAppearance.Text.Center
- TextAppearance.Title2
- TextAppearance.Title2.Bold.Center
- TextAppearance.Title2.Center
- TextPickerView
- UIThemingConfigurations
- WebAuthnViewController
OAuthLifecycle
With the OAuthTokenModel
at hand, it is possible to request the server to refresh the currently held token in order to be able to continue accessing the resources for longer without having to go through the Haapi Flow
.
To do so, an OAuthLifecycle
API is provided and the user is able to easily refresh the token by using it like below.
// if OAuthTokenModel's refreshToken is nil or empty, we cannot execute the refresh token request.
if let refreshToken = (oauthModel as? OAuthTokenModel)?.refreshToken, !refreshToken.isEmpty {
OAuthLifecycle.refreshToken(refreshToken: refreshToken,
haapiUIKitApplication: haapiUIKitApplication,
lifecycleResultListener: self)// self is an instance of OAuthLifecycleResultListener
}
Configuring external apps integration
When integrating with external providers, third party apps required for the authentication flow that are launched from within the application must have their scheme's declared in the app's plist info file. The third party app's scheme
list must be declared in the Queried URL Schemes
configuration section like below. You can find relevant information here.
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>bankid</string>
...
<string>other_app_scheme</string>
</array>
</dict>
</plist>
Autofill for username and password text fields
Username and password autoFill are supported when running the Haapi flow. However, it is possible to improve the user experience by setting up the application’s associated domains as explained here.
HaapiLogger
When using IdsvrHaapiUIKit, it is possible to display the logs in the console as demonstrated below.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// ...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ...
HaapiLogger.followUpTags = DriverFollowUpTag.allCases + SdkFollowUpTag.allCases + UIKitFollowUpTag.allCases // To filter logs.
return true
}
// ...
}
Display logs by configuring HaapiLogger.followUpTags
// Driver, Sdk, and UIKit logs are displayed in the console
HaapiLogger.followUpTags = DriverFollowUpTag.allCases + SdkFollowUpTag.allCases + UIKitFollowUpTag.allCases
// Only UIKit logs are displayed in the console.
HaapiLogger.followUpTags = UIKitFollowUpTag.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 enabled | Default value |
---|---|---|
Debug | HaapiLogger.isDebugEnabled | It is set to true in DEBUG mode. Otherwise, default value is false but can be set by the developer if needed. |
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:
- HAAPI_DRIVER_FLOW: Logs related to the driver flow.
- HAAPI_DRIVER_STORAGE: Logs related to the storage in the driver
- HAAPI_DRIVER_HTTP: : Logs related to any http calls in the driver.
- HAAPI_DRIVER_ATTESTATION: Logs related to the attestation flow.
To filter Sdk logs, use the prefix: HAAPI_SDK
. Here are the following up tags for the sdk:
- HAAPI_SDK_FLOW: Logs related to the sdk flow.
- HAAPI_SDK_HTTP: Logs related to any http calls in the sdk.
- HAAPI_SDK_MAPPING: Logs related to the mapping in the sdk.
- HAAPI_SDK_OAUTH: Logs related to OAuth in the sdk.
To filter UI logs, use the prefix: HAAPI_UI
. Here are the following up tags for the UIKit:
- HAAPI_UI_DATA: Logs related to the data in the UI.
- HAAPI_UI_COMPONENT: Logs related to the components in the UI.
- HAAPI_UI_THEMING: Logs related to the theming in the UI.
- HAAPI_UI_FLOW: Logs related to the flow in the UI.
- HAAPI_UI_SWIFTUI: Logs related to SwiftUI in the UI.
- HAAPI_UI_MAPPING: Logs related to the mapping in the UI.
- HAAPI_UI_LAYOUT: Logs related to the layout of the displayed component.
Write logs to another destination
When using IdsvrHaapiDriver, it is possible to write the logs to another destination as demonstrated below.
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())
Error handling - IdsvrError
When using the HaapiIdsvrUIKit
framework's functional classes, it's possible that their respective completion handlers can return an Error
. This error can be cast to an HaapiUIKitError
as demonstrated below:
private func handleError(_ error: Error) {
guard let haapiUIKitError = error as? HaapiUIKitError else { return }
switch haapiUIKitError {
case .illegalState(let reason): reason
case .other(let error): error
case .unsupported(let cause): cause
case .unsupportedMap(let objName, let expectedObjName): expectedObjName, objName
}
An HaapiUIKitError conforms to IdsvrError
that provides metadata describing why an error occurred and provides more information about the error. It is possible to cast an HaapiUIKitError to IdsvrError
and get the following information:
- error: The error code.
- errorDescription: The error description.
- failureReason: The failure reason.
- recoverySuggestion: The suggestion for handling this Error.
- cause: The cause of the error. (Optional)
private func handleHaapiError(_ error: HaapiUIKitError) {
guard let idsvrError = 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`.
}
}
Additional note
If more control over transport/serialization or custom behavior is needed, the HAAPI iOS Driver and HAAPI iOS SDK can be used as they define lower-level building blocks to issue HAAPI requests.
Reference Documentation
Protocols
- ClientOperationStep
- DataMapper
- FollowUpTag
- GenericClientOperationHandler
- HaapiConfigurable
- HaapiDeepLinkManageable
- HaapiFlowDeepLinkable
- HaapiFlowResult
- HaapiFlowResultViewController
- HaapiRepresentation
- HaapiUIKitApplication
- IdsvrError
- LogSink
- Masking
- OAuthDataMapper
- OAuthErrorModel
- OAuthModel
- OAuthTokenConfigurable
- OAuthTokenModel
- ProblemRepresentation
- Properties
- RepresentationActionModel
- Storage
- TokenBoundConfiguration
- TokenEndpointResponseListener
- UIInteractionModel
- UIModel
- UIOperationModel
- UIProblemModel
- ViewLoadable
Structs
- AttestationConfiguration
- AuthenticatorSelectorStep
- AuthenticatorSelectorStep.AuthenticatorOption
- BankIdClientOperationStep
- ClientOperationAction.Properties
- ContinueSameStep
- EncapClientOperationStep
- ErrorTokenResponse
- ExternalBrowserClientOperationStep
- FormAction.Properties
- FormActionModel
- GenericClientOperationStep
- GenericProperties
- GenericRepresentationStep
- HaapiFlowVCRepresentable
- HaapiFlowViewControllerRepresentable
- HaapiUIKitConfiguration
- InteractiveFormStep
- InvalidInputProblem.InvalidField
- Link
- Message
- Metadata
- OAuthAuthorizationParameters
- OAuthAuthorizationResponseProperties
- OAuthAuthorizationResponseStep
- PollingProperties
- PollingStep
- RedirectionStep
- ResponseAndData
- SelectFormField.Option
- SelectorAction.Properties
- SelectorActionModel
- SuccessfulTokenResponse
- UserConsentStep
- UserMessage
- WebAuthnAuthenticationClientOperationStep
- WebAuthnRegistrationClientOperationStep
Classes
- AccessToken
- Action
- AuthorizationProblem
- BankIdClientOperationActionModel
- BoundedTokenConfiguration
- CheckboxFormField
- ClientOperationAction
- ClientOperationActionModel
- ContextFormField
- Dpop
- DpopAccessTokenInfo
- EncapAutoActivationClientOperationActionModel
- ErrorHTTPURLResponseContent
- ExternalBrowserClientOperationActionModel
- FormAction
- FormField
- GenericClientOperationActionModel
- HTTPURLResponseContent
- Haapi
- HaapiClient
- HaapiConfiguration
- HaapiDeepLinkManager
- HaapiFlow
- HaapiFlowViewController
- HaapiFlowViewModel
- HaapiLogger
- HaapiManager
- HaapiTokenManager
- HaapiTokenManagerBuilder
- HaapiTokenResult
- HaapiUIKitApplicationBuilder
- HaapiUIKitConfigurationBuilder
- HiddenFormField
- InvalidInputProblem
- KeychainStorage
- OAuthLifecycle
- OAuthTokenManager
- OAuthTokenManager.ErrorTokenHTTPURLResponseContent
- OAuthTokenManager.SuccessTokenHTTPURLResponseContent
- PasswordFormField
- Problem
- SelectFormField
- SelectorAction
- TextFormField
- UnboundedTokenConfiguration
- UsernameFormField
- WebAuthnAuthenticationClientOperationActionModel
- WebAuthnAuthenticationClientOperationActionModel.AllowedCredential
- WebAuthnAuthenticationClientOperationActionModel.CredentialRequestOptions
- WebAuthnRegistrationClientOperationActionModel
- WebAuthnRegistrationClientOperationActionModel.CredentialParams
- WebAuthnRegistrationClientOperationActionModel.CredentialRequestOptions
- WebAuthnRegistrationClientOperationActionModel.CrossPlatformCredentialRequestOptions
- WebAuthnRegistrationClientOperationActionModel.ExcludedCredential
- WebAuthnRegistrationClientOperationActionModel.PlatformCredentialRequestOptions
Enums
- ActionKind
- AuthenticatorSelectionPresentation
- ClientOperationName
- CryptoKeyType
- DriverFollowUpTag
- HaapiError
- HaapiModel
- HaapiModel.ActionFactory
- HaapiModel.ActionModelFactory
- HaapiModel.ContentFactory
- HaapiModel.FormFieldFactory
- HaapiModel.ProblemFactory
- HaapiModel.PropertiesFactory
- HaapiModel.StepFactory
- HaapiModel.TokenResponseFactory
- HaapiResult
- HaapiUIKitError
- HttpHeaderNames
- IdsvrErrorHandling
- LogType
- MimeTypes
- PollingStatus
- PresentationMode
- ProblemType
- RepresentationType
- RetryCondition
- SdkFollowUpTag
- StorageError
- TestingArgument
- TestingEnvironment
- TextFormField.Kind
- TokenResponse
- TokenRevocationResponse
- UIKitFollowUpTag
- UnrecoverableAction
Extensions
- ActionKind
- ActionTemplate
- ClientOperationName
- Data
- Dictionary
- FollowUpTag
- FormFieldType
- HaapiFlow
- HaapiFlowViewController
- HaapiTokenManager
- HaapiUIKitError
- JOSEHeader
- NSObject
- PollingStatus
- ProblemType
- Publisher
- RepresentationType
- Signer
- StorageError
- String
- StringProtocol
- TextPickerView
- UIImage
- UIInteractionModel
- UIModel
- UIOperationModel
- UIProblemModel
- URL
- URLRequest
- URLResponse
- URLSessionConfiguration
- Verifier
Typealiases
- ActionKind.RawValue
- ActionTemplate.RawValue
- ClientOperationName.RawValue
- FormFieldType.RawValue
- HaapiCompletionHandler
- HaapiFlowViewControllerRepresentable.UIViewControllerType
- HttpHeadersProvider
- Kind.RawValue
- OAuthAuthorizationParametersProvider
- OAuthCompletion
- OAuthLifecycleResultListener
- OAuthRevocationCompletion
- PollingStatus.RawValue
- ProblemType.RawValue
- RepresentationType.RawValue