Skip to main content
Skip table of contents

TrustBuilder ID SDK (iOS)

The SDK is available on demand. Please contact your TrustBuilder's point of contact.

The TrustBuilder ID SDK allows you to develop a mobile app that runs natively on a device. Native mobile applications can use native or browser-based login flows.

In a hosted login flow, the user is shown a web browser and redirected to the TrustBuilder hosted login page for sign up or log in. For example: an iOS application opens a SafariViewController or an Android application opens a Custom Chrome Tab.

With a native login flow, the user signs up or enters their password directly into the app. Note, however, that when using external IDPs, an iOS app may still open a SafariViewController and an Android app may still open a Custom Chrome Tab for the interaction with external IDPs.

Regardless of which option you choose, TrustBuilder supports either.

 

TrustBuilder ID SDK is a mobile SDK for communicating using OAuth 2.0 and OpenID Connect. It maps the raw protocol flows and adds convenience methods to assist with common tasks like performing an action with fresh tokens and to obtain user profile information.

2a3a7689-4a3d-48dd-8aa5-701c9e227f9d.png

Best practices

The TrustBuilder ID SDK wraps following functionality:

  • Secure protocol handling

  • Secure token lifecycle

  • Secure user profile access

  • Persona-driven policies

  • Handle session obligations

  • Handle onboarding obligations

  • Blacklist checking

Phishing/security concerns: an unauthorized party could decompile or intercept traffic to/from your application to get the Client ID and authentication URL. With this information the unauthorized party could create a rogue application, upload it to an application store, and use it to phish for usernames, passwords, and Access Tokens.

An authorization request is dispatched to the TrustBuilder.IO AuthorizationService instance, and the response will be dispatched to the activity of your choice, expressed via an Intent. The TrustBuilder ID SDK supports the PKCE extension to OAuth which was created to secure authorization codes in public clients when custom URI scheme redirects are used. The TrustBuilder ID SDK encapsulates the authorization state of the user and communicates with the TrustBuilder.IO platform.

Token requests, such as obtaining a new access token using a refresh token, follow a similar pattern and are dispatched to TrustBuilder.IO, and a token response instance is returned via a callback. Responses update the authorization state in order to track and persist changes. Once in an authorized state, access tokens can be automatically refreshed as necessary before performing actions that require valid tokens.

The TrustBuilder ID SDK follows best practice set out in RFC 8252 - OAuth 2.0 for Native Apps, authorization requests from native apps should only be made through external user-agents, primarily the user's browser. For this reason, WebView is explicitly not supported for security reasons.RFC 8252 OAuth 2.0 for Native Apps. Using a web browser for OAuth and OIDC is not only a security imperative but also a user experience advantage. By leveraging trusted browser environments and educating users on the benefits, we can ensure a seamless and secure authentication process.

  • RFC 8252 Guidelines as defined by IETF

    • https://datatracker.ietf.org/doc/html/rfc8252

    •  OAuth 2.0 and Native Apps: OAuth 2.0 authorization requests from native apps should only be made through external user-agents, primarily the user's browser. This is a security best practice as outlined in RFC 8252.

    • Prohibition of Embedded User-Agents: Native apps must not use embedded user-agents for authorization requests. Embedded user-agents can compromise security by allowing access to the user's full authentication credentials.

  • Google's Stance on WebView

  • System Browser as a Secure Alternative

  • Platform standard implementation

    • iOS and Android: Both platforms provide specific ways to initiate authorization in a browser (e.g., iOS's SFSafariViewController/SFAuthenticationSession and Android's Custom Tab feature), ensuring a seamless and secure experience.

    • Google's OAuth Policies: Google's OAuth policies emphasize the use of browsers over embedded webviews, aligning with security best practices. (https://developers.google.com/identity/protocols/oauth2/policies#browsers )

  • User Experience Considerations

    • Trust and Familiarity: Users generally trust their browser environment. Features like address verification in browsers enhance trust compared to embedded webviews.

    • Platform Standards and Security: The embedded browser is a platform standard for both iOS and Android. Prioritizing security, users can benefit from the familiar interface of their system browser.

    • Educating Users: Instead of compromising security, educating users through an onboarding flow can enhance their understanding and acceptance of browser-based authentication.

    • Cancellation handling: Because having alternative flows when the user intentionally closes could be desired, we allow checking on that specific flow.

1. Configure TrustBuilder

To use TrustBuilder services, you need to have an application set up in the TrustBuilder Dashboard. The TrustBuilder application is where you will configure authentication in your project.

1.2 Configure your application

Use the Admin Portal to configure a new TrustBuilder application or select an existing application that represents the project you want to integrate with. Every application in TrustBuilder is assigned an alphanumeric, unique client ID that your application code will use to call TrustBuilder APIs through the SDK.

1.3 Configure callback URLs

A callback URL is the application URL that TrustBuilder will direct your users to once they have authenticated. If you do not set this value, TrustBuilder will not return users to your application after they log in.

1.4 Configure logout URLs

A logout URL is the application URL TrustBuilder will redirect your users to once they log out. If you do not set this value, users will not be able to log out from your application and will receive an error.

2. Install the TrustBuilder ID SDK

2.1 Integration

Using the .xcframework

In order to use the framework you need to link it in your project. To link it into the demo project, do the following steps:

  1. Go to the `General` tab of **target** of your application.

  2. Click on `+` in the `Frameworks, Libraries and Embedded Content` section.

  3. Click on `Add other...` and afterwards `Add files...` and select the .xcframework file from the output folder

  4. You should finally end with the framework being under the `Frameworks, Libraries and Embedded Content` section. You can then start importing the framework.

Installing pods

Install cocoa pods in the project and add the following lines in the Podfile for SSZipArchive dependencies.

CODE
target 'your target' do
  use_frameworks!
  pod 'SSZipArchive', '2.4.3'
end

2.2 Creating the TBLoginService

In order to use any logic related to the TBLoginService, ensure that you have created an instance of the TBLoginService  by simply constructing it using the public constructor:

CODE
let tbLoginService = TBLoginService(tenantUrl: "<your tenant url>",
                                    authorizationEndpoint: URL(string: "<your authorization endpoint>")!,
                                    tokenEndpoint: URL(string: "<your token endpoint>")!,
                                    clientId: "<your client id>",
                                    clientSecret: "<your client secret>",
                                    redirectURI: URL(string: "<your configured redirect url>")!,
                                    scopes: ["openid", ...],
                                    tokenRefreshThreshold: 5 * 60 // 5 minutes)

Of course, ensure that the URL creation didn't return a nil value.
This instance will allow you to access all of the behaviour that is facilitated in this SDK.
Behind the scenes, we will also retrieve a fresh access token automatically when trying to retrieve information from the UserInfo API.

  • tenantUrl
    The tenantUrl will be the baseUrl of your Trustbuilder tenant. The correct endpoints are automatically called by the SDK. An example of a tenantUrl can be yourcompany.trustbuilder.io

  • clientId
    OAuth 2.0 Client Identifier valid at the Authorization Server.

  • clientSecret

    The client's secret that is related to the given clientId.

  • redirectURI

    Redirection URI to which the response will be sent. This URI MUST exactly match one of the Redirection URI values for the Client pre-registered at the TrustBuilder portal under the correct Service Provider(simple string comparison)
    In our example we use com.trustbuilder.identitydemo://login-callback

  • scopes

    The list of scopes for which the authentication process will take place.

  • tokenRefreshThreshold

    The tokenRefreshThreshold will be used during the check whether a token is still valid or not. This way it's possible to already refresh a token a bit before it's actually expired. This must be compatible with the Access Token TTL configured in the TrustBuilder portal.

2.3 Creating the TBOnboardingService

In order to use any logic related to the TBOnboardingService, ensure that you have created an instance of the TBOnboardingService by simply constructing it using the public constructor:

CODE
onboardingService = TBOnboardingService(tenantUrl: Constants.tenantUrl,
                                        documentIds: Constants.belgianDocumentIds,
                                        documentConfiguration: nil,
                                        selfieConfiguration: nil)

2.4 AppDelegate

Because the Trustbuilder SDK needs to hold a session to listen to callbacks from the redirect, a specific method handleOpenedURL needs to be called from the following method in your AppDelegate: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application .
An example implementation of how you correctly pass the information to the TrustBuilder can be found below:

CODE
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return TBAppDelegateHandler.instance.handleOpenedURL(url)
}

2.5 Signing in with 3rd party authenticators

In order to sign in using a 3rd party authenticator that will open an external application for eg. BankID, Itsme... some additional setup needs to be done.
Because of the flow, these 3rd party authenticators need to navigate back to your application. This is done by deep linking, specifically by using URL Schemes.

To set this up in your application, you must dd the identifier that you use in the configured redirectURI and was confirmed with Trustbuilder. In our example we use com.trustbuilder.identitydemo://login-callback as a redirectURI, meaning that com.trustbuilder.identitydemo as our identifier.
The setup was already done in the info.plist file in the example app under the CFBundleURLTypes.

3. TBLoginService - Hosted Login

The authentication flow via the browser can be triggered using the authenticateWithBrowser method on your instance of TBLoginService.

On iOS you will also need to pass in the UIViewController that will be used as the presenting ViewController.

The SDK returns either a successful result when we were able to authenticate the user, or a specific Error when we were not.
This could be due to different reasons eg.: User cancellation, Invalid client secret, ...

Usage

CODE

tbLoginService.authenticateWithBrowser(viewController: self) { result in
    switch result {
    case .success(_):
        debugPrint("The result was successful, the user is signed in.")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

3.2 Accessing the token

When later in your application you want to access the token to authorize your api calls with, you can simply use getAndRefreshTokenWhenNecessary on that same TBLoginService instance.
You are technically not required to use that same instance, but you can create a new one with the same configuration.

This method will check whether the token is at least valid for the time in the given threshold when the instance TBLoginService was created.
When that is not the case, it will try to refresh the access token and return the refreshed one.

Usage

CODE
tbLoginService.getTokenAndRefreshWhenNeeded { result in
    switch result {
    case .success(let token):
        debugPrint("The fresh token is \(token)")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

3.3 User canceled flow

It is possible to check whether the user has intentionally cancelled the flow.
This can be done by checking whether the error object in the .failure result is a TBError.userCancellation.

4. TBLoginService - Native Login

The authentication flow that can be more customized from a UI perspective, contains 2 major steps. Both of these steps can be triggered on your instance of TBLoginService .

4.1 Get Identity Providers

The first step is to retrieve all the identity providers that can be used to authenticate the user. This can be done using the getAllowedIDPs method on the instance of TBLoginService.
That method will return a list of TBIdentityProvider. That list can be used to present the different sign-in options to the user.

 

Usage

CODE
tbLoginService.getAllowedIDPs { result in
    switch result {
    case .success(let idps):
        debugPrint("The result was successful")
        // show the results to the user
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

4.2 Authenticate

When the user has selected the identity provider, the authentication flow can then be triggered by using the authenticateWith method on the instance of TBLoginService .

On iOS you will also need to pass in the UIViewController that will be used as the presenting ViewController.

The SDK returns either a successful result when we were able to authenticate the user, or a specific Error when we were not.
This could be due to different reasons eg.: User cancellation, Invalid client secret, ...

Usage

CODE
self.tbLoginService.authenticateWith(idp, viewController: self) { result in
    switch result {
    case .success(_):
        debugPrint("The result was successful")
        self.isSignedIn = true
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
        self.isSignedIn = false
    }
}

4.3 Accessing the token

When later in your application you want to access the token to authorize your api calls with, you can simply use getAndRefreshTokenWhenNecessary on that same TBLoginService instance.
You are technically not required to use that same instance, but you can create a new one with the same configuration.
This method will check whether the token is at least valid for the time in the given threshold when the instance TBLoginService was created.
When that is not the case, it will try to refresh the access token and return the refreshed one.

Usage

CODE
tbLoginService.getTokenAndRefreshWhenNeeded { result in
    switch result {
    case .success(let token):
        debugPrint("The fresh token is \(token)")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

4.4 User canceled flow

It is possible to check whether the user has intentionally cancelled the flow.
This can be done by checking whether the error object in the .failure result is a TBError.userCancellation.

5. TBLoginService - UserInfo

The information that can be retrieved from can also be accessed from the same TBLoginService instance.

5.1 Get user details

In order to retrieve the details for the currently signed in User, the method getCurrentUser(completion: @escaping (Result<TBUser, Error>) -> Void) -> Void can be used on the same instance of TBLoginService.
This method will retrieve the information about the current user and return it in the form of a TBUser.

The current user can be defined as the user where TB.Identity currently holds a session for.

Usage

CODE
tbService.getCurrentUser() { result in
    switch result {
    case .success(let data):
        debugPrint("The result was successful")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

5.2 Patch User Details

In order to update some attributes of the user, the retrieved TBUser instance can simply be updated and sent to the SDK with the patchUser(_ user: TBUser, completion: @escaping (Result<TBUser, Error>) -> Void) -> Void method on the same TBLoginService instance.

Usage

CODE
user.familyName = "<updated familyName"
                
self.tbService.patchUser(user){ result in
    switch result {
    case .success(_):
        debugPrint("The result was successful")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

6. TBLoginService - Personas

6.1 Get list of personas

In order to retrieve the list of personas for the current signed in user. You can use the getListOfPersonas(completion: @escaping (Result<[TBPersona], Error>) -> Void) method on the instance of TBLoginService.

Behind the scenes, we will automatically use the information of the currently signed in user to retrieve the personas that the user can select.

Usage

CODE
tbLoginService.fetchPersonasForCurrentUser { result in
    switch result {
    case .success(let personas):
        debugPrint("The result was successful for personas")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

6.2 Select persona

In order to retrieve a single persona based on a given personaId you can use the selectPersona(_ persona: TBPersona, completion: @escaping (Result<TBPersonaInformation, Error>) -> Void) method on the same instance of TBLoginService. Based on the currently signed in user and the given persona, the detail information of that persona will be retrieved.

When there is no user signed in we will return a .failure Result with a TBError.notSignedIn  error.

Usage

CODE
tbLoginService.selectPersona(persona) { result in
    switch result {
    case .success(let persona):
        debugPrint("The result was successful for persona")
    case .failure(let error):
        debugPrint("The error we got was: \(error)")
    }
}

6.3 Update persona attributes

In order to update the attributes of a specific persona, you can use updatePersonaAttributes(of persona: TBPersona, completion: @escaping (Result<TBPersonaInformation, Error>) -> Void).
This method expects the originally retrieved instance of TBPersonaInformation from selectPersona with the updated attributes. We need this original instance to correctly update the information. Hence there is also no public constructor available of TBPersonaInformation.

Usage

CODE
private func patchAttributes(for persona: TBPersonaInformation) {
    persona.attributes.editableAttributes.forEach {
        // You can edit the values of an existing attribute
        $0.values = ["TRUE"]
    }
    
    // Here we delete every attribute besides the first attribute.
    persona.attributes.editableAttributes = [persona.attributes.editableAttributes.first!]
    
    tbService.patchPersonaAttributes(for: persona) { result in
        switch result {
        case .success(_):
            debugPrint("The result was successfully patched for persona")
        case .failure(let error):
            debugPrint("The error we got was: \(error)")
        }
    }
}

7. Response Handling

Every call returns a Result value with the following specifications: https://developer.apple.com/documentation/swift/result.

When the result is a .failure we will store a specific Error.
Some errors are defined by TrustBuilder. This list can be found below.

  • TBError

    • TBError.notSignedIn: Will be returned when we do not have information about any signed in user.

    • TBError.incorrectTokenInformation: Will be returned when we could not retrieve the user information from the stored token.

    • TBError.couldNotCreateURL: Will be returned when we could not create the correct url with the given url information and the retrieved user information from the access token.

    • TBError.objectWithoutEtag: Will be returned when the send parameter to patch a given item does not contain an ETag. Without this we cannot update the given object.

  • TBNetworkError

    • TBNetworkError.invalidResponse: Will be returned when we could not retrieve the response from the network call.

    • TBNetworkError.couldNotEncodeToJSON: Will be returned when we could not encode the given to valid JSON.

    • TBNetworkError.invalidStatusCode: Will be returned when we have an invalid response, in case of validation errors, these will also be parsed into a TBValidationError object.

    • TBNetworkError.currentResourceOutdated: Will be returned when trying to patch an object that has already been updated between retrieval and the update.

8. TBOnBoardingService

In order to use the onboarding service, we need to have access to the camera of the device.
Ensure to add the NSCameraUsageDescription permission in your info.plist file.

8.1 Start Document Onboarding

This method will execute the following flow:

  1. Start a validation session

  2. Start the document scanner to scan documents and upload said documents

  3. Start the selfie scanner and upload said selfie

  4. Request and return the validation result.

The result will contain a

  • Validation ID

  • Indication whether the verification was successful.

Requirements:

In order to start the onboarding flow you need a bearer token of an authenticated user with the correct claims. 
You also need to pass in a UIViewController so the scanners can be started. In our usage block self is the UIViewController .
You can use your instance of onboardingService to start this flow.

Usage

CODE
onboardingService.startOnboarding(with: "ACCESS TOKEN", and: self) { [self] result in
    onboardButton.isLoading = false
    switch result {
    case .success(let result):
        debugPrint("The onboarding was executed with validation: \(result.verified)")
    case .failure(let error):
        debugPrint("The onboarding was unsuccessful, the error we got was: \(error)")
    }
}

8.2 Session expired

Behind the scenes we will start a validation session that will only be valid for a certain amount of time. If the end-user takes to long to complete the flow, there might be an unsuccessful response with a TBError.sessionExpired error.

 

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.