Delete mobile PIN

Deletes an app-specific PIN code previously 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 unbinds the Mosaic mobile app from the device by deleting cryptographic seed and PIN code used to protect it. Once deleted, the user can no longer authenticate using the Mobile PIN Authentication step.

The PIN-based credentials are bound to a specific device. When deleting a PIN code, the user may be provided implicitly by the journey if already authenticated; otherwise, a user identifier must be specified in the step configuration.

When triggered, this step instructs the client to list devices with configured PIN codes and prompt the user to delete a PIN code for current device. The client uses the Authentication SDK to perform the client-side deregistration (removing the cryptographic seed) while the SDK signals Mosaic to revoke credentials associated with the key ID. The client sends this result back to the journey using the Orchestration SDK.

The deletion is only finalized once the journey responds with a PinCodeUnregistrationCommit instruction, which the client must handle by invoking unregistrationContext().commit(). If PIN deletion fails or is cancelled, the journey either aborts or proceeds to a failure branch, depending on configuration.

To support PIN deletion, you must:

  • Implement the Authentication SDK calls required for deleting (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). "

Example

After requesting to delete a PIN code, the client uses the Authentication SDK to deregister the PIN-protected credential. The resulting public key, key ID, and key type are removed. The result is then submitted to the journey using the Orchestration SDK.

The journey responds with a PinCodeUnregistrationCommit instruction, which the client must handle by calling unregistrationContext().commit() to finalize the PIN deletion.

kotlinswift
Copy
Copied
data class PinCodeDeletionInput(@SerializedName("publicKeyId") val publicKeyId: String)

private fun processServiceResponse(idoResponse: TSIdoServiceResponse) {
    when (idoResponse.journeyStepId) {
        TSIdoJourneyActionType.PinCodeDeletion.type -> unregisterPinCode(userId)
    }
}

private fun unregisterPinCode(userId: String) {
        TSAuthentication.unregisterPinCode(userId, object : TSAuthCallback<TSPinCodeUnregistrationResult, TSPinCodeUnregistrationError> {
            override fun error(error: TSPinCodeUnregistrationError) {
                //TODO: handle error
            }

            override fun success(result: TSPinCodeUnregistrationResult) {
                TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, PinCodeDeletionInput(
                    publicKeyId = result.keyId()
                ),
                object: TSIdoCallback<TSIdoServiceResponse> {
                    override fun idoSuccess(serviceResponse: TSIdoServiceResponse) {
                        //TODO: handle service response
                    }

                    override fun idoError(error: TSIdoSdkError) {
                        //TODO: handle error
                    }

                    override fun idoInstruction(instruction: TSIdoInstruction) {
                        commitPinCodeUnregistration(result, instruction)
                    }
                })
            }
        })
    }

    private fun commitPinCodeUnregistration(result: TSPinCodeUnregistrationResult, instruction: TSIdoInstruction) {
        val keyID = instruction.data?.optString("public_key_id")
        if (instruction.type == TSIdoInstructionType.PinCodeUnregistrationCommit
            && result.keyId() == keyID) {
                result.unregistrationContext().commit()
        } else {
            //TODO: handle key id or instruction type mismatch
        }
    }
Copy
Copied
private func runPinCodeDeletionAction() {
    TSAuthentication.shared.initialize(baseUrl: "your_base_url",
                      clientId: "your_client_id")

    Task {
      do {
        let result = try await TSAuthentication.shared.unregisterPinCode(username: "userIdentifier")

        outputData.publicKeyId = result.publicKeyId

        submitClientResponse(clientResponseOptionId: .clientInput, data: ["publicKeyId": result.publicKeyId]) { instruction in

          guard let publicKeyId = instruction.data["public_key_id"] as? String else { return }

          if instruction.type == .PinCodeUnregistrationCommit,
            result.publicKeyId == publicKeyId {
            do {
              try result.unregistrationContext.commit()
            } catch {
              debugPrint("[ERROR] Pin deregistration failed with error: \(error)")
            }
          }
        }

      } catch {
        goToNextStep(clientResponseOption: .fail)
        debugPrint("[ERROR] Pin deregistration failed with error: \(error)")
      }
    }
  }

  func submitClientResponse(clientResponseOptionId: TSIdoClientResponseOptionType, data: [String: Any]?, instructions: TSIdo.TSSDKInstructionsCallback?) {
    do {
      try TSIdo.submitClientResponse(clientResponseOptionId: clientResponseOptionId, data: data, handleInstruction: instructions)
    } catch {
      debugPrint("Journey error")
    }
  }