Register Mobile PIN
Registers an app-specific PIN code chosen by the user for authentication
About client-facing steps
A journey is a sequence of steps that are executed when it's invoked by a client application (known as the "client"). Some steps require involvement from the client, such as to collect user input. Other steps, like validating a token, are executed in the backend by the Mosaic journey engine alone.
When invoked, the journey begins executing the steps while the client waits for further instructions. When a journey reaches a client-facing step, the journey asks the client for the required input and then waits for the client to respond. The client presents their own UI if user interaction is required, and returns input to the journey. The journey then proceeds in a similar manner until it's completed.
Description
This client-facing step is used to bind the Mosaic mobile app to the device by generating an encrypted key pair protected by a PIN code chosen by the user. Once registered, the user can authenticate using the Mobile PIN Authentication step.
The PIN-based credentials are bound to a specific device. The user may be provided implicitly by the journey if already authenticated; otherwise, a user identifier must be specified in the step configuration. PIN registration should only occur after the user has verified their identity (e.g., via OTP, document verification, or an external system).
When triggered, this step instructs the client to prompt the user to create their app-specific PIN code. The client uses the Authentication SDK to perform the client-side registration by generating and protecting a secret seed with the chosen PIN. The combination of the seed and the user-provided PIN is used to derive a unique key pair that can later be used to sign challenges. Once registration succeeds, the SDK returns the key ID, public key, and key type. The client sends this result back to the journey using the Orchestration SDK.
The registration is only finalized once the journey responds with a PinCodeRegistrationCommit
instruction, which the client must handle by invoking registrationContext().commit()
. If registration fails or is cancelled, the journey either aborts or proceeds to a cancel/failure branch, depending on configuration.
To support PIN registration, you must:
- Configure the Register Mobile PIN journey step
- Implement the Authentication SDK calls required for registration (see Example )
- Implement the Orchestration SDK call to submit the client response back to the journey
Note
Configuration
Field | Description |
---|---|
User Auth State | Indicates if the user has authenticated in this journey. - If set to The user is authenticated (default), the user context is provided implicitly by the journey.- If set to The user is not authenticated , a user identifier must be provided. If the identifier doesn’t exist, a Mosaic user record is automatically created. |
External user id | User identifier, specified as an expression. Only configured if the journey doesn't authenticate the user before invoking this step. |
Error Output Variable | Name of the variable that stores any errors returned by the step |
Failure Behavior | Determines the behavior in case of failure, which either aborts the journey or proceeds to a failure branch of the control flow (default). |
Custom Branches | Additional journey branches supported for this step. The client can select a branch by returning the branch ID. For each branch, you can define a schema for the information that the client is expected to return (used by the code generator and for autocompleting journey expressions) and a display name to label it in the editor. |
Branch Output Variable | Name of the variable used to store the data returned by the client, which can be used in subsequent journey steps. |
Cancel Behavior | Determines the behavior of client cancellation, which either aborts the journey (default) or proceeds to a cancel branch of the control flow |
Example
After prompting the user for their app-specific PIN code, the client uses the Authentication SDK to register the PIN-protected credential. The resulting public key, key ID, and key type are then submitted to the journey using the Orchestration SDK.
The journey responds with a PinCodeRegistrationCommit
instruction, which the client must handle by calling registrationContext().commit()
to finalize the registration.
// Registers a new PIN code for the given user
private func registerPINCode(username: String, pinCode: String) {
// Start PIN registration using the Authentication SDK
TSAuthentication.shared.registerPinCode(username: username, pinCode: pinCode) { result in
switch result {
case .success(let response):
do {
// Submit the public key, key ID, and key type to the journey
let data: [String: Any] = [
"publicKey": response.publicKey,
"publicKeyId": response.publicKeyId,
"keyType": response.keyType
]
// Send the registration result to the journey and wait for instruction
try TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: data) { instruction in
// Handle commit instruction if required
self.commitPinCodeRegistration(response, instruction: instruction)
}
} catch {
// Handle IDO submission error
debugPrint("[ERROR] Pin Registration failed with error: \(error)")
}
case .failure(let error):
// Handle registration error
debugPrint("[ERROR] Pin Registration failed with error: \(error)")
}
}
}
// Commits the registration if the backend instruction is valid
private func commitPinCodeRegistration(_ response: TSPinCodeRegistrationResult, instruction: TSIdoInstruction) {
// Validate instruction type and match the public key ID
if instruction.type == .pinCodeRegistrationCommit,
let publicKeyId = instruction.data["public_key_id"] as? String,
response.publicKeyId == publicKeyId {
do {
// Finalize registration by committing it via SDK
try response.registrationContext.commit()
} catch {
// Handle commit error
debugPrint("[ERROR] Pin Registration commit failed with error: \(error)")
}
} else {
// Handle mismatch between expected and actual key ID
debugPrint("[ERROR] Pin Registration commit failed: invalid instruction type or public key id mismatch")
}
}
// Data model for the registration result to send back to the journey
data class PinCodeRegistrationInput(
@SerializedName("publicKey") val publicKey: String,
@SerializedName("publicKeyId") val publicKeyId: String,
@SerializedName("keyType") val keyType: String
)
fun registerPinCode(userId: String, pinCode: String) {
// Start PIN registration using the Authentication SDK
TSAuthentication.registerPinCode(userId, pinCode, object : TSAuthCallback<TSPinCodeRegistrationResult, TSPinCodeRegistrationError> {
override fun error(error: TSPinCodeRegistrationError) {
// Handle registration error
showError(error.errorMessage)
Log.e("PinCodeFragment", "Pin code registration error: " + error.errorMessage, error.throwable)
}
override fun success(result: TSPinCodeRegistrationResult) {
// Submit the public key, key ID, and key type to the journey
TSIdo.submitClientResponse(
TSIdoClientResponseOptionType.ClientInput.type,
PinCodeRegistrationInput(
result.publicKey(),
result.keyId(),
result.keyType()
),
object: TSIdoCallback<TSIdoServiceResponse> {
override fun idoSuccess(serviceResponse: TSIdoServiceResponse) {
// Continue journey with service response
processIDOResponse(serviceResponse)
}
override fun idoError(error: TSIdoSdkError) {
// Handle IDO submission error
processIDOError(error)
}
override fun idoInstruction(instruction: TSIdoInstruction) {
// Handle commit instruction if required
commitPinCodeRegistration(result, instruction)
}
}
)
}
})
}
private fun commitPinCodeRegistration(result: TSPinCodeRegistrationResult, instruction: TSIdoInstruction) {
// Validate instruction type and match the public key ID
val keyID = instruction.data?.optString("public_key_id")
if (instruction.type == TSIdoInstructionType.PinCodeRegistrationCommit
&& result.keyId() == keyID) {
// Finalize registration by committing it via SDK
result.registrationContext().commit()
} else {
// Handle mismatch between expected and actual key ID
showError("Result key id and instruction key id mismatch")
}
}