Although the XignIn technology allows users to authenticate in a comfortable way even when only a PIN is used, a core concept is to support multiple authentication factors either as alternatives or in conjunction, depending on the respective service configuration, to meet security requirements as well as the users needs with respect to usable security.
While some authenticators are automatically created during the personalization process, others may require further interaction with the user who might want to skip creation of certain authenticators at first but needs them later on to be able to use specific services. Therefore, the SDK offers a way to add certain authenticators to an existing activation. Currently, only the biometric factor falls into this category but the process of adding an authenticator will in general stay the same.
Note: Each factor can only be added once per activation. If for example the biometric factor (see Creating a BiometricFactor) becomes invalid due to changes of the biometric samples enrolled in the operating system, the factor can only be re-enrolled by activating the app again.
For security reasons, the user has to be authenticated before a new factor can be registered. The following code shows
an example on how the SDK provided functionality to do so can be used. Note that since the SDK supports one activation
per XignIn-Manager, an idmIdentifier is required to identify which activation the authenticator should be added to.
func startAddFactor(
idmIdentifier: String,
authenticatorType: AuthenticatorType = .biometric
) throws {
// Check whether an authentication factor of this type can be added at all.
// This check should be performed early in the process, since forcing the
// user to authenticate although the factor can not be added will result
// in a poor user experience.
if !(YourImplementation.isAuthenticationFactorSupportedByDevice(authenticatorType)) {
// The demanded authenticator type is not supported and can therefore not be added.
// For example a biometric factor can not be added if the device has no biometric sensors.
throw YourError.yourErrorCase
}
// Retrieve the personalization component used to update the activation
let personalizer: Personalizer = XignSdk.shared.personalizer
// Fetch authentication initialization data from the SDK.
// An authentication is required to add another authentication
// factor for security reasons.
let authInitData: AuthenticationInitializationData =
try personalizer.startAddAuthenticationFactorSynchronous(
idmIdentifier: idmIdentifier,
factorType: authenticatorType
)
// Perform a regular authentication as described in authentication chapter
let authenticationResult: AuthenticationResult =
try YourImplementation.authenticateUser(authInitData)
// Check the authentication result. In order to be able to add a factor,
// a result of type `.apiAccessAddFactor` is required. All other results
// in this process have to be considered and handled as errors.
switch authenticationResult {
case .apiAccessAddFactor(let apiAccessResult):
// Return the api access data that authorizes
// adding another authentication factor
try prepareAuthenticationFactor(apiAccessResult, authenticatorType)
case .userLogin,
.serviceLogin,
.apiAccessChangePin,
.apiAccessRequestNewActivationData,
.apiAccessDelete,
.apiAccessMergeComplete:
throw YourError.yourErrorCase
case .lockout:
// Receiving a lockout does not indicate an error in the process
// flow and has to be handled differently. Please refer to the
// section 'ErrorHandling' for detailed information.
YourImplementation.handleLockoutAndRepeatAuthentication()
}
}
fun startAddFactor(
activity: AppCompatActivity,
idmIdentifier: String,
authenticatorType: AuthenticatorType = AuthenticatorType.BIOMETRIC
) {
// Check whether an authentication factor of this type can be added at all.
// This check should be performed early in the process, since forcing the
// user to authenticate although the factor can not be added will result
// in a poor user experience.
if (!YourImplementation.isAuthenticationFactorSupportedByDevice(authenticatorType)) {
// The demanded authenticator type is not supported and can therefore not be added.
// For example a biometric factor can not be added if the device has no biometric sensors.
throw YourException()
}
// Retrieve the personalization component used to update the activation
val personalizer: Personalizer = XignSdk.shared.personalizer
// Fetch authentication initialization data from the SDK.
// An authentication is required to add another authentication
// factor for security reasons.
val authInitData: AuthenticationInitializationData =
personalizer.startAddAuthenticationFactorSynchronous(
idmIdentifier = idmIdentifier,
factorType = authenticatorType
)
// Perform a regular authentication as described in authentication chapter
val authenticationResult: AuthenticationResult =
YourImplementation.authenticateUser(authInitData)
// Check the authentication result. In order to be able to add a factor,
// a result of type `AuthenticationResult.ApiAccessAddFactor` is required.
// All other results in this process have to be considered and handled as errors.
when (authenticationResult) {
is ApiAccessAddFactor -> {
// Return the api access data that authorizes
// adding another authentication factor
prepareAuthenticationFactor(activity, authenticationResult, authenticatorType)
}
is ApiAccessChangePin,
is ApiAccessMergeComplete,
is ApiAccessRequestNewActivationData,
is ServiceLoginResult,
is ApiAccessDelete,
is UserLoginResult -> {
throw YourException()
}
is Lockout -> {
// Receiving a lockout does not indicate an error in the process
// flow and has to be handled differently. Please refer to the
// section 'ErrorHandling' for detailed information.
YourImplementation.handleLockoutAndRepeatAuthentication()
}
}
}
After the user has been successfully authenticated, the factor itself has to be prepared. The details of this step depend heavily on the authenticator type but since currently only the biometric factor can be added after a personalization, the following code example shows an emphasis on this factor type and can only refer to the general approach in case of other types.
func prepareAuthenticationFactor(
_ apiAccessResult: ApiAccessAddFactor,
_ authenticatorType: AuthenticatorType
) throws {
// Retrieve the personalization component used to update the activation.
let personalizer: Personalizer = XignSdk.shared.personalizer
// Depending on the factor type, preparation may differ significantly.
if authenticatorType == .biometric {
// This example combines preparation and actual adding of
// the biometric factor but these steps can also be
// performed separately as shown in the more general
// `else` case.
prepareAndAddBiometricFactor(apiAccessResult: apiAccessResult)
} else {
// Prepare the factor depending on its type.
let factor: AuthenticationFactor =
try YourImplementation.prepareAuthenticationFactor(
apiAccessResult,
authenticatorType
)
// Finally, add the factor itself.
try personalizer.finishAddAuthenticationFactorSynchronous(
result: apiAccessResult,
factor: factor
)
}
}
fun prepareAuthenticationFactor(
activity: AppCompatActivity,
apiAccessResult: ApiAccessAddFactor,
authenticatorType: AuthenticatorType
) {
// Retrieve the personalization component used to update the activation
val personalizer: Personalizer = XignSdk.shared.personalizer
// Depending on the factor type, preparation may differ significantly.
if (authenticatorType == AuthenticatorType.BIOMETRIC) {
// This example combines preparation and actual adding of
// the biometric factor but these steps can also be
// performed separately as shown in the more general
// `else` case.
prepareAndAddBiometricFactor(activity, apiAccessResult)
} else {
// Prepare the factor depending on its type
val factor: AuthenticationFactor =
YourImplementation.prepareAuthenticationFactor(
apiAccessResult,
authenticatorType
)
// Finally, add the factor itself.
personalizer.finishAddAuthenticationFactorSynchronous(
result = apiAccessResult,
factor = factor
)
}
}
For the case of adding a biometric factor, the details of preparing and adding the factor can be combined, which is shown in the following code example describing the completion of the process.
func prepareAndAddBiometricFactor(apiAccessResult: ApiAccessAddFactor) {
// Retrieve the idmIdentifier which is used to determine which
// activation is about to be changed by adding a biometric factor.
let idmIdentifier: String = apiAccessResult.idmIdentifier
// Create and authenticate a biometric factor as described
// in the section 'Creating a BiometricFactor'
do {
try getBiometricFactor(idmIdentifier: idmIdentifier) { biometricFactorOpt in
if let biometricFactor = biometricFactorOpt {
// After a successful factor authentication by the user
// adding the factor can be finished
XignSdk.shared.personalizer.finishAddAuthenticationFactor(
result: apiAccessResult,
factor: biometricFactor
)
} else {
// Handle and display errors
YourImplementation.handleTheError()
}
}
} catch {
// Failed to start evaluate access control.
YourImplementation.handleTheError()
}
}
fun prepareAndAddBiometricFactor(
activity: AppCompatActivity,
apiAccessResult: ApiAccessAddFactor
) {
// Create and authenticate a biometric factor as
// described in the section 'Creating a BiometricFactor'
getBiometricFactor(
idmIdentifier = apiAccessResult.idmIdentifier,
activity = activity,
) { biometricFactor ->
if (biometricFactor == null) {
// Handle and display errors
throw YourException()
} else {
// After the BiometricFactor has been successfully
// created and authenticated by the user,
// adding the factor can be finished.
XignSdk.shared.personalizer.finishAddAuthenticationFactor(
result = apiAccessResult,
factor = biometricFactor
)
}
}
}