Skip to content

Interaction with the SDK

Initialization

The Trinity ID Wallet SDK is a context-specific singleton which is initialized once for the runtime of your application. In order to initialize the instance, it requires access to the application context so user interactions can be started.

To initialize the instance, just call TrinityIDWallet.init(trinityIDWalletConfiguration) method with the instance of Trinity ID Wallet configuration.

import androidx.appcompat.app.AppCompatActivity
import de.comuny.trinity.Authentication
import de.comuny.trinity.TrinityConfiguration
import de.comuny.trinity.TrinityIDWallet
import de.comuny.trinity.TrustProviderType
import de.comuny.trinity.util.ComunyCertificates

class MyActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val environmentUrl = "https://oidc.cls.comuny.de"
        val environmentCertificates = listOf(
            ComunyCertificates.comunySelfHostedSubordinateCertificate,
            ComunyCertificates.comunySelfHostedRootCertificate,
        )
        val sdk = TrinityIDWallet.init(
            TrinityConfiguration(
                applicationContext,
                environmentUrl,
                environmentCertificates,
                TrustProviderType.Build38(Authentication.PIN),
                ModuleRegistry(
                    TwilioModule,
                    SampleModule,
                )
            )
        )

        //now, you can receive the instance anywhere
        val sdk = TrinityIDWallet.INSTANCE
    }
}

TrinityConfiguration contains all the necessary parameters as application context, an environment URL, a list of environment certificates, a trust provider type and an module registry. Trinity ID Wallet SDK can be configured with different security and authentication in mind, which is reflected by trust provider type parameter. You can either use the Build38’s T.A.K. or android keystore security layer. The authentication method can be Authentication.Biometric (default), Authentication.PIN or Authentication.None.

This init method returns SDK instance, which can be used in the project. If you don't want to manage SDK's instance, you can call TrinityIDWallet.INSTANCE to get it from anywhere.

Application's or Activity's onCreate method is good place where to put the initialization block. Be aware, initialization of Trinity ID Wallet SDK can take up to 400ms, which is too long for the main thread if you want from your app to run smoothly.

Trinity configuration

In order to create Trinity ID Wallet SDK, you have to provide TrinityConfiguration. You have to provide environment's url and certificates, what kind of trust provider type and authentication you want to use and you also have to specify modules that are going to be used by SDK.

There are also few optional attributes, that can be specified but are not required as there are default values already provided. By providing TrinityTimeouts object you can specify timeouts that Trinity ID Wallet SDK will respect (by default, all timeouts are set to 2 minutes) or if you choose to use Build38’s T.A.K. trust provider type, you can specify path to licence file.

Trust providers

Trinity ID Wallet SDK might use different security layers to manage device and app security called “trust providers”. These frameworks make sure that all data the Trinity SDKs operate is trusted and secure.

Build38’s T.A.K. (recommended to use)

If you choose to use Build38’s T.A.K. as trust provider, behavior might change based on your license file. Keep in mind, that in the case of prevention of attack, T.A.K. might crash the app immediately.

Learn more about T.A.K here: Build38’s T.A.K. webside.

Android Keystore (recommended for development use)

Android Keystore system is a native way how to protects key material from unauthorized use.

Learn more about Keystore here: Android Keystore system.

Logical components

After initialization, you can see, Trinity ID Wallet SDK is divided into 6 sections:

Authentication

In order to be able to use SDK functionality, user has to be authenticated. You can trigger user authentication manually by calling authenticate method, or it will happen automatically during authorization or resolving a module.

Trying to access the sdk storage or consents without authentication will result in an exception.

class CustomViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun toggleAuthentication(activity: FragmentActivity) {
        viewModelScope.launch {
            val isAuthenticated = sdk.authentication.authenticated
            if (isAuthenticated)
                sdk.authentication.authenticate().collect { event ->
                    when(event) {
                        // biometric authentication
                        is AuthenticationEvent.BiometricAuthenticationEvent.OnBiometric -> {
                            event.authenticate(activity)
                        }

                        // pin authentication
                        is AuthenticationEvent.PinAuthenticationEvent.OnCreatePin -> {
                            // handle UI for gathering PIN from the user and then
                            event.createPin("pin")
                        }
                        is AuthenticationEvent.PinAuthenticationEvent.OnVerifyPin -> {
                            // handle UI for gathering PIN from the user and then
                            event.authenticate("pin")
                        }
                        is AuthenticationEvent.PinAuthenticationEvent.OnPinAuthenticationError -> {
                            // show error message that can occur during pin verification
                            println(event.message)
                        }

                        is AuthenticationEvent.OnNoAuthenticationRequired -> {
                            // no authentication is required, nothing to do really
                        }
                        is AuthenticationEvent.OnAuthenticated -> {
                            // authentication successful
                        }
                    }

                    // or you can call event.cancel()
                }
            else
                sdk.authentication.revokeAccess()

            println(sdk.authentication.lastAuthentication.toString())
            if (sdk.authentication.lastAuthentication < (Clock.System.now() - 60.seconds)) {
                // authenticate user anyway
                sdk.authentication.authenticate(force = true)
            }
        }
    }
}

You can also read the last time user's authentication was performed by lastAuthentication property, or you can also force the authentication, even if user is already authenticated by calling authenticate method with force parameter set to true.

In order to be compliant with OIDC standard, SDK also supports OIDC authentication. You can read whether user performed OIDC authentication by calling oidcAuthentication or you can also revoke it by calling revokeOidcAuthentication().

Authorization

After you scan authUrl from QR code, you can use authorize or authorizeManually methods to perform an authorization. Difference is, that in authorizeManually you have to call each step (start, resolveMissingClaims, finish) manually. To better understand this section, see also resolving of a module section in this documentation.

class CustomViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun authorize(authUrl: Url, activity: Activity) {
        viewModelScope.launch {
            val authorizationInput = AuthorizationInput(
                authUrl,
                "subject",
            )

            //automatic
            sdk.authorization.authorize(authorizationInput).collect {
                when (it) {
                    is AuthenticationEvent -> handleAuthenticationEvent(it) // see documentation about authentication
                    is ResolvingEvent.OnMultipleModules -> handleResolvingEvent(it) // see documentation about resolving a module
                    is ResolvingEvent.OnResolve -> handleResolvingEvent(it) // see documentation about resolving a module
                    is ResolvingEvent.OnResolveModuleEvent -> handleResolvingEvent(it) // see documentation about resolving a module
                    is ResolvingEvent.OnResolved -> handleResolvingEvent(it) // see documentation about resolving a module

                    AuthorizationEvent.OnStart -> {
                        println("starting authorization")
                    }
                    is AuthorizationEvent.OnStarted -> {
                        println("authorization started with claims:")
                        println(it.claimNames.joinToString())
                    }
                    is AuthorizationEvent.OnConsentRequired -> {
                        // show UI for the user
                        println("user has to agree with the transmission of the claims:")
                        it.confirm()
                        // or it.decline()
                    }
                    is AuthorizationEvent.OnFinish -> {
                        println("finishing authorization")
                    }
                    is AuthorizationEvent.OnFinished -> {
                        println("authorization finished")
                    }
                }
            }

            //manual
            val manualAuthorization = sdk.authorization.authorizeManually(authorizationInput)
            manualAuthorization.start().collect {
                // OnStart, AuthenticationEvent, OnStarted
                // see documentation about handling authorization events from authorize method above
            }
            manualAuthorization.resolveMissingClaims().collect {
                // OnMultipleModules, OnResolve, OnResolved and any module related OnResolveModuleEvent
                // see documentation about resolving modules
            }
            manualAuthorization.finish().collect {
                // OnFinish, OnConsentRequired, OnFinished
                // see documentation about handling authorization events from authorize method above
            }
        }
    }
}

Most of the events are optional to integrate, but you can leverage from the state to better inform end user about the process. However, events like pin authentication events, AuthorizationState.Resolving.OnMultipleOptions and AuthorizationState.Finish.OnConsentRequired or some resolving events are mandatory to implement, but they don't have to arrive in all cases! SDK requires response to those events for successful authorization.

Don't forget to implement mandatory events: AuthorizationState.Resolving.OnMultipleModules AuthorizationState.Finish.OnConsentRequired

You can receive AuthorizationEvent.OnConsentRequired event in case that approval from user is required to transmit claims to the client. You are required to show it to the user and call confirm() or decline() function based on user's response. You can read following values from the event: claims: List<ClaimName>, clientId: String.

Consents

For interaction with the consents, user needs to be authenticated.

User consents

You can read in any time, what consents user approved to which clients with this SDK components. User can also decide to decline a consent, which can be done by calling deleteConsent(clientId: String, claimName: ClaimName) function.

class ConsentsViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun read() {
        viewModelScope.launch {
            val consent: Consent? = sdk.consents.readConsent("clientId", StandardClaimName.Name)
            val allConsents: List<Consent> = sdk.consents.readAllConsents()
        }
    }

    fun delete() {
        viewModelScope.launch {
            sdk.consents.deleteConsent("clientId", StandardClaimName.Name)
            sdk.consents.deleteAll()
        }
    }
}

By calling readAllConsentLogs() you can access the history of consents.

Following operations are logged:

  • creation of consent
    • user approves consent during authorization
  • deletion of consent
    • consent was deleted manually
    • user declines consent during authorization if consent prompt was enforced
  • authorization
    • authorization of data to the client because consent was given before
1
2
3
4
5
6
7
class ConsentLogsViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun readActivities() {
        viewModelScope.launch {
            val consentHistory: List<ConsentLog> = sdk.consents.readAllConsentLogs()
        }
    }
}

Modules resolving

Modules section can be used for resolving of a module upfront, before authorization will happen.

class CustomViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun resolveModules(moduleClaimName: ModuleClaimName, activity: Activity) {
        viewModelScope.launch {
            sdk.modules.resolve(moduleClaimName).collect {
                when (it) {
                    is AuthenticationEvent -> handleAuthenticationEvent(it) // see documentation about authentication

                    is ResolvingEvent.OnMultipleModules -> {
                        // show UI for the user
                        println("user has to select which standard claim (${it.requestedClaimNames}) wants to authorize: ${it.possibleModules}")
                        it.selectModules(it.possibleModules.take(1))
                        // or it.cancel()
                    }
                    is ResolvingEvent.OnResolve -> {
                        println("starting resolving of ${it.claimName}")
                    }
                    is ResolvingEvent.OnResolveModuleEvent -> {
                        // here you have to handle every module specific event
                        // for more details, see each module documentation
                    }
                    is ResolvingEvent.OnResolved -> {
                        println("resolving of module finished with claim ${it.claim}")
                    }
                }
            }
        }
    }
}

Multiple modules event

You can receive ResolvingEvent.OnMultipleModules event in case of authorization of standard claim that can be resolved by multiple modules. You are required to call selectModules function to confirm, which modules you want to resolve. It's up to you, if you want to show UI to the user, or read a storage to check, whether claims are already resolved. You can read following values from the event: requestedClaimNames: List<ClaimName>, possibleModules: List<TrinityModule<*,*>>.

Alternatively you can also ask the module registry directly to know which module is able to resolve the claim.

val claimName = event.claimName
val modules = sdk.modules.moduleRegistry.getModulesFor(claimName)

Module specific events

Interface ResolvingEvent.OnResolveModuleEvent represents events, that came from each module specifically. Every module contains its own set of events. Some are just informative, but others requires response. For more information, see module documentation. (Twilio)

Storage

For interaction with the storage, user needs to be authenticated.

For every claim that can be specified during the authorize request, a corresponding dataset is stored in the wallet - along with some metadata. Interactions with the wallet to retrieve data can be methods which automatically map values to readable objects or via reading the raw values.

class CustomViewModel(private val sdk: TrinityIDWallet) : ViewModel() {
    fun read() {
        viewModelScope.launch {
            val fullAuthadaModuleClaimValue: ModuleClaimData<out AuthadaModuleClaimValue> = sdk.storage.readModuleClaim(AuthadaModule)
            val specificAuthadaClaimValue: SpecificClaimData<out String> = sdk.storage.readSpecificClaim(AuthadaSpecificClaimName.AuthadaGivenName)
            val standardClaimValue: List<StandardClaimData<out Date>> = sdk.storage.readStandardClaim(StandardClaimName.Birthdate)
            val rawClaimValue = sdk.storage.readRawClaim(ClaimName.from("comuny.ver.given_name.l2.eid.authada"))
        }
    }
}
  • Module claims: full representation of resolved claim
  • Specific claims: every module specify it's set of specific claims
  • Standard claims: oidc standard claims

Nuke

The SDK provides a function called nuke() to reset the entire SDK. That will also delete all entries from the claim and consent storage. This operation can not be undone.