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
For Android, the Authentication SDK requires `compileSdk` 34 (or greater) and `minSdk` 23.

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.

SwiftKotlin
Copy
Copied
// 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")
    }
}
Copy
Copied
// 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")
    }
}