The personalization, also called activation, is the process of establishing the user account on a smart device. Authentication processes can only be performed after the device has been successfully activated for the corresponding XignIn-Manager.
One prerequisite for a personalization is the user's registration at a XignIn-Manager. During the registration the user has to choose, or in some cases is automatically given, an activation PIN which, depending on the XignIn-Managers configuration, has to be 5 to 32 digits long. As a result of the registration process, personalization initialization data are generated. These must be made available to the user in the form of a QR Code or a Deep-Link.
The following diagram shows the flow of the personalization process:
The process of the personalization starts by parsing the personalization initialization data. Please take a look at the
section Starting a process of the XignSys SDK for that matter. With
the PersonalizationInitializationData the next step can be initiated by invoking
Personalizer.decryptPersonalizationInitializationData(data:pin:). As the second parameter this function expects the
user's activation PIN that has for example been specified in the registration process but could also have been changed
later on.
private func startPersonalization(
_ personalizationInitializationData: PersonalizationInitializationData
) throws {
// Request the activation PIN from the user.
let activationPin: String = YourImplementation.askUserForPin()
let personalizer: Personalizer = XignSdk.shared.personalizer
let response: PersonalizationResponse
do {
response = try personalizer.decryptPersonalizationInitializationDataSynchronous(
data: personalizationInitializationData,
pin: activationPin
)
} catch let lockoutError as XignSdkLockoutError {
// Happens if the user is still locked out, due to an previous attempt and
// the lockout period was not waited out.
let information: PendingLockoutInformation = lockoutError.information
// Wait out the period given by the `PendingLockoutInformation` object.
// After that, the whole process must be started from the beginning; scanning the QR code.
throw YourError.yourErrorCase
} catch let xignError as XignSdkError {
let errorCode = DecryptPersonalizationInitializationDataErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourError.yourErrorCase
} catch {
// Should not happen normally, because the SDK wraps all of its errors within a
// XignSdkError. A general error should be displayed here, just in case.
throw YourError.yourErrorCase
}
switch response {
case .rePersonalization(let parameters, let newActivationTrigger):
// Indicates that the server already has a device personalized with these pid.
// This can be another device but can also be the same device the personalization process
// is currently performed with, e.g., if the SDK has been reset in the meantime.
// Either override the personalization with the `parameters` or request a new activation
// with the `newActivationTrigger`.
return
case .newPersonalization(let parameters):
// Indicates the pid have never been used for a complete personalization before.
// Continue the personalization with the PersonalizationParameters object.
return
case .alreadyPersonalized(let parameters):
// Indicates that this device is already personalized. You can either override the
// existing personalization if for example you wish to re-enroll a biometric factor.
// Otherwise, the process can be canceled at this point.
return
case .mergePersonalization(let parameters, let mergeData):
// Indicates that an account merge can be performed with the 'mergeData' or the
// existing personalization can be replaced with the new one by continuing the
// normal personalization process with the 'parameters'.
return
case .lockout(
lockoutInformation: let lockoutInformation,
personalizationInitializationData: let personalizationInitializationData
):
// Indicates that the given factor for the authentication was invalid, e.g. most likely
// the wrong PIN was entered by the user. In this case the lockout period must be waited
// out given by `lockoutInformation`. After that time the process can be repeated
// with the given personalizationInitializationData.
// Check if the lockout is permanent.
if lockoutInformation.isPermanent {
// The activation has been disabled permanently, notify the user an cancel the process.
throw YourError.yourErrorCase
}
// Retrieve the date until which the lock is effective.
let lockedUntil: Date = lockoutInformation.lockedUntil
// Wait until the lock has been lifted.
// Retry the personalization from the beginning.
try startPersonalization(personalizationInitializationData)
return
}
}
private fun startPersonalization(
personalizationInitializationData: PersonalizationInitializationData,
) {
// Request the activation PIN from the user.
val activationPin: String = YourImplementation.askUserForPin()
val personalizer = XignSdk.shared.personalizer
val response: PersonalizationResponse = try {
personalizer.decryptPersonalizationInitializationDataSynchronous(
data = personalizationInitializationData,
pin = activationPin
)
} catch (lockoutError: XignSdkLockoutException) {
// Happens if the user is still locked out, due to a previous attempt and
// the lockout period was not waited out.
val information: PendingLockoutInformation = lockoutError.information
// Wait out the period given by the `PendingLockoutInformation` object.
// After that, the whole process must be started from the beginning; scanning the QR code.
throw YourException()
} catch (xignError: XignSdkException) {
val errorCode = DecryptPersonalizationInitializationDataErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourException()
} catch (t: Throwable) {
// Should not happen normally, because the SDK wraps all of its exceptions within a
// XignSdkException. A general error should be displayed here, just in case.
throw YourException()
}
when (response) {
is PersonalizationResponse.RePersonalization -> {
val parameters: PersonalizationParameters = response.parameters
val newActivationTrigger: NewActivationTrigger = response.trigger
// Indicates that the server already has a device personalized with these `pid`.
// This can be another device but can also be the same device the personalization process
// is currently performed with, e.g., if the SDK has been reset in the meantime.
// Either override the personalization with the `parameters` or request a new activation
// with the `newActivationTrigger`.
return
}
is PersonalizationResponse.NewPersonalization -> {
val parameters: PersonalizationParameters = response.parameters
// Indicates the pid have never been used for a complete personalization before.
// Continue the personalization with the PersonalizationParameters object.
return
}
is PersonalizationResponse.AlreadyPersonalized -> {
val parameters: PersonalizationParameters = response.parameters
// Indicates that this device is already personalized. You can either override the
// existing personalization if for example you wish to re-enroll a biometric factor.
// Otherwise, the process can be canceled at this point.
return
}
is PersonalizationResponse.MergePersonalization -> {
val parameters: PersonalizationParameters = response.parameters
val mergeData: AuthenticationInitializationDataWithSession = response.mergeData
// Indicates that an account merge can be performed with the 'mergeData' or the
// existing personalization can be replaced with the new one by continuing the
// normal personalization process with the 'parameters'.
return
}
is PersonalizationResponse.Lockout -> {
val lockoutInformation: LockoutAttemptInformation = response.lockoutInformation
val personalizationInitializationData: PersonalizationInitializationData =
response.personalizationInitializationData
// Indicates that the given factor for the authentication was invalid, e.g. most likely
// the wrong PIN was entered by the user. In this case the lockout period must be waited
// out given by `lockoutInformation`. After that time the process can be repeated
// with the given personalizationInitializationData.
// Check if the lockout is permanent.
if (lockoutInformation.isPermanent) {
// The activation has been disabled permanently, notify the user to cancel the process.
throw YourException()
}
// Retrieve the date until which the lock is effective.
val lockedUntil: Instant = lockoutInformation.lockedUntil
// Wait until the lock has been lifted.
// Retry the personalization from the beginning.
startPersonalization(personalizationInitializationData)
return
}
}
}
The function decryptPersonalizationInitializationData(data:pin:) can throw an Error (Exception). All errors thrown
by functions of the XignSys SDK are of the type XignSdkError (XignSdkException).
The XignSdkError (XignSdkException) contains a parameter named errorCode of the type XignSdkErrorCodes, which is
an enum. This enum contains all possible errors that can be raised by the XignSys SDK. These errorCode can be mapped
to a more specific enum, which only reflects errors concerning the
function decryptPersonalizationInitializationData(data:pin:) by
invoking DecryptPersonalizationInitializationDataErrorCodes.from(xignError.errorCode). These error codes should be
handled according to their documentation, which in most cases should lead to displaying an error message to the user.
In addition to the normal XignSdkError (XignSdkException), the
function decryptPersonalizationInitializationData(data:pin:) can also throw more specific subclass of
the XignSdkError (XignSdkException): the XignSdkLockoutError (XignSdkLockoutException). This error needs a
little more handling. It describes a state where the user has previously failed an authentication check, mostly likely
due to supplying an invalid Activation PIN, and the given lockout period has not passed yet. In this case the user
must be informed about the lockout duration returned by the PendingLockoutInformation of the information property of
the XignSdkLockoutError (XignSdkLockoutException). As the lockout handling is mentioned at multiple locations within
this documentation, it is discussed in the chapter Lockout handling.
Another more specific type of the XignSdkError (XignSdkException) that can be raised at this point is the
XignSdkSecurityInsufficientError (XignSdkSecurityInsufficientException). It is thrown if the XignIn-Manager rejects
the device/app because of an insufficient security level. For more information on how to handle the error please check
the chapter security check.
Regardless of which error is thrown by the function decryptPersonalizationInitializationData(data:pin:) the process
stops at this point. If the process is to be retried, it should be started again from the very beginning, e.g. by
scanning of the activation QR Code.
In case no error is thrown, the result of decrypting the personalization initialization data is an instance
of PersonalizationResponse. This is an enum with five cases in Swift or a sealed class with five implementations
in Kotlin. Every case that can be continued directly carries a parameter (property) named parameters of the type
PersonalizationParameters which can be used to personalize this particular activation.
The case newPersonlization marks a new activation that was never personalized before. Its parameters can simply be
used to continue the process.
The case rePersonlization denotes an activation that has already been personalized at some point in the past. If the
previous personalization is still in use/active cannot be told for sure. Therefore, two possible actions are offered to
continue the process depicted by the properties parameters and newActivationTrigger. The parameters can be used to
override the personalization, meaning that the personalization of the device/app that still uses this activation becomes
invalid. Make sure to notify the user about this action before continuing. Alternatively, a new activation can be
requested with the
newActivationTrigger to finish the personalization. This leaves the original activation intact and creates another
one. The newActivationTrigger variable is of the type NewActivationTrigger.
Before the process can be continued with that object it must be supplemented with at least a new activation PIN by
invoking NewActivationTrigger.pinSpec.setPin(:). An alias can also be specified optionally. The alias is used as a
display name of the activation, e.g. during a recovery. Furthermore, if the user should receive a backup email with the
activation data after the process is finished, the property NewActivationTrigger.shouldSendEmail can be set to true.
Note: Not all XignIn-Managers are configured to send e-mails to users. This can for example be the case in instances where sending mails is handled by a third party or not supported at all.
The case alreadyPersonalized indicates that the device is already personalized. This can happen if the user scans the
personalization QR code again after the activation of the same device was already successful. This scenario is not
classified as an error because it can be used to re-enroll a previously enrolled biometric factor, that may have been
invalidated by the system due to changes made to the biometric samples by the user. Alternatively, the process can be
stopped at this point because the device is already personalized.
The case mergePersonlization is a special case which can only occur, if the user has already registered and
personalized and receives an organization invite with a new account. The organization invite is a feature of the
XignIn-Manager that offers the possibility to invite users to an organization or a specific client. This can currently
be initiated with an email address. If the XignIn-Manager finds an account with the given email address, an organization
invite mail is send which the user can accept or decline. However, if no matching account can be found a new temporary
registration is automatically generated for the given email address. In case the user already has an account, but with a
different email address, scanning the QR code of the new registration within an already activated app leads to
the mergePersonlization case. This case contains two objects PersonalizationParameters
and AuthenticationInitializationDataWithSession. The PersonalizationParameters can be used to replace the current
activation with the new one within the XignSys SDK.
Note: The current activation will only be removed from the SDK and not deleted, it can still be used on another app/device or re-activated at some point in the future.
If the current activation is replaced, the new temporary registration becomes permanent and a new account is created.
After that, the account can no longer be merged with another one. Otherwise,
the AuthenticationInitializationDataWithSession can be used to merge the two accounts. In this case, the temporary
registration is deleted and the email address is added to the existing account. To confirm this an authentication
process must be completed which can be started with the AuthenticationInitializationDataWithSession by
invoking Authenticator.startAuthenticationSynchronous(data:). For further details about the authentication process,
please refer to the chapter Starting an authentication.
The last case lockout can occur if the given authentication factor is invalid, for example if a wrong activation PIN
is supplied. In this case two parameters are returned, the lockoutInformation and
the personalizationInitializationData. The lockoutInformation is of the type LockoutAttemptInformation and
contains a period of time the user must wait until the process can be repeated. For more information about the lockout
handling, please refer to the chapter Lockout Handling. If possible, the process can be repeated
with the given personalizationInitializationData by invoking
Personalizer.decryptPersonalizationInitializationDataSynchronous again, after the given lockout duration has passed.
With the PersonalizationParameters or supplemented NewActivationTrigger the personalization process can be finished
by either invoking Personalizer.personalizeIdentityManagerSynchronous(parameters:fireBasePushToken:biometricFactor:)or
Personalizer.personalizeIdentityManagerSynchronous(trigger:fireBasePushToken:biometricFactor:) respectively.
This function takes two optional parameters: a fireBasePushToken: String? and a biometricFactor: BiometricFactor?.
Both parameters are for convenience only and if omitted, the respective configuration can be done at a later time.
If the XignSys Push Module, described in the chapter Push, is used, the Firebase push token can be passed to immediately enable the XignSys push functionality for this activation. Please be aware that users may not wish to enable push messages right away and should therefore be asked for consent, especially from a data privacy perspective. If no consent is given at the time or there is any other reason, why a push token should not be configured within the activation process, the parameter can be omitted and the Firebase push token can be set later on.
The BiometricFactor however is needed, if the user should be able to authenticate against a service using biometrics.
Creating a BiometricFactor is discussed in the chapter Creating a BiometricFactor.
If omitted, the personalization will be provisionally completed without a biometric authenticator. The BiometricFactor
can also be added afterwards, if it was skipped during a personalization
(see Add Factor). Beware though, that the biometric factor can only be added once,
regardless of whether it was set during the activation or afterwards.
The following example shows how a personalization can be finished.
private func finishPersonalization(
parameters: PersonalizationParameters,
fireBasePushToken: String?,
biometricFactor: BiometricFactor?
) throws {
let result: PersonalizationResult
do {
result = try XignSdk.shared.personalizer.personalizeIdentityManagerSynchronous(
parameters: parameters,
fireBasePushToken: fireBasePushToken,
biometricFactor: biometricFactor
)
} catch let xignError as XignSdkError {
let errorCode = PersonalizeIdentityManagerErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourError.yourErrorCase
} catch {
// Should not happen normally, because the SDK wraps all its errors within a
// XignSdkError. A general error should be displayed here, just in case.
throw YourError.yourErrorCase
}
let idmIdentifier: String = result.idmIdentifier
}
private fun finishPersonalization(
parameters: PersonalizationParameters,
fireBasePushToken: String?,
biometricFactor: BiometricFactor?
) {
val result: PersonalizationResult = try {
XignSdk.shared.personalizer.personalizeIdentityManagerSynchronous(
parameters = parameters,
fireBasePushToken = fireBasePushToken,
biometricFactor = biometricFactor
)
} catch (xignError: XignSdkException) {
val errorCode = PersonalizeIdentityManagerErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourException()
} catch (t: Throwable) {
// Should not happen normally, because the SDK wraps all of its exceptions within a
// XignSdkException. A general error should be displayed here, just in case.
throw YourException()
}
val idmIdentifier: String = result.idmIdentifier
}
At this point the personalization process is finished. The PersonalizationResult only carries some meta information
that do not need any explicit handling.
Note: If the app should only support a single XignIn-Manager, it is recommended to save the
idmIdentifierin the preferences of the app. The XignSys SDK is designed to support multiple XignIn-Managers so this identifier is used to start processes that can modify the user's configuration, for example to change the users PIN or to add aBiometricFactorafter a personalization.