Mobile PIN Authentication

Authenticates the user using an app-specific PIN code chosen by the user

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 authenticate the user using an app-specific PIN code they registered previously (using the Register Mobile PIN step).

Once triggered, this step instructs the client to prompt the user for their PIN and perform a signing operation on a challenge provided by the journey. The client uses the Authentication SDK together with the code and the challenge to generate a signature, and submits the result back to the journey using the Orchestration SDK.

If successful, the journey sets the user context to the authenticated user and continues to the next step. User tokens generated upon authentication can be accessed in subsequent steps using @policy.userContext(). If the step fails or is cancelled, the journey either aborts or continues to a failure or cancel branch if configured.

To support mobile PIN authentication, you'll need to:

  • Implement PIN registration as described in Register Mobile PIN
  • Implement the Authentication SDK call required for authentication (see Example )
  • Implement the Orchestration SDK call that submits the authentication result (see Android reference or iOS reference )
  • Configure the Mobile PIN Authentication journey step
Note
For Android, the Authentication SDK requires `compileSdk` 34 (or greater) and `minSdk` 23.

Configuration

Field Description
User identifier source Specifies the source of the user identifier. The username can either be provided by the client upon authentication (default) or provided by the journey (after resolving the configured source expression).
External user identifier User identifier, specified as an expression. Must be configured only if the journey is configured as the username source.
Org context Only in B2B journeys. Determines the organization for which the step is executed. By default, the step uses the org context previously set in the journey (e.g., using Select organization step). If set to "manual", you can provide an expression that yields the organization ID.
Error Output Variable Name of the variable that stores any errors returned by action
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

When executed, this step sends a callback to the client with the IDO service response object. It includes a challenge to be signed using the registered PIN credentials. The client prompts the user to enter their app-specific PIN, performs the signing operation using the Authentication SDK, and submits the signed result back to the journey using the Orchestration SDK.

SwiftKotlin
Copy
Copied
private func authenticatePINCode(idoResponse: TSIdoServiceResponse, pinCode: String) {
    // Extract user identifier and challenge from the IDO response
    guard let username = idoResponse.data?["user_identifier"] as? String else {
        debugPrint("[ERROR] User identifier not found in IDO response")
        return
    }
    
    guard let challenge = idoResponse.data?["pin_challenge"] as? String else {
        debugPrint("[ERROR] Pin challenge not found in IDO response")
        return
    }
    
    // Authenticate the user using the provided PIN and challenge
    TSAuthentication.shared.authenticatePinCode(username: username, pinCode: pinCode, challenge: challenge) { result in
        switch result {
        case .success(let response):
            do {
                // Build the result payload to send back to the journey
                let data: [String: Any] = [
                    "publicKeyId": response.publicKeyId,
                    "challenge": response.challenge,
                    "signature": response.signature,
                    "userIdentifier": username
                ]

                // Submit the authentication result to the journey
                try TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: data)
            } catch {
                debugPrint("[ERROR] Pin Authentication failed with error: \(error)")
            }
        case .failure(let error):
            // Handle authentication failure
            debugPrint("[ERROR] Pin Authentication failed with error: \(error)")
        }
    }
}
Copy
Copied
// Data model for the authentication result to send back to the journey
data class PinCodeAuthenticationInput(
    @SerializedName("publicKeyId") val publicKeyId: String,
    @SerializedName("signature") val signature: String,
    @SerializedName("userIdentifier") val userIdentifier: String
)

fun authenticatePinCode(idoResponse: TSIdoServiceResponse, pinCode: String) {
    // Extract user identifier and challenge from the IDO response
    val userId = idoResponse.responseData?.optString("user_identifier")
    val challenge = idoResponse.responseData?.optString("pin_challenge")

    // Authenticate the user using the provided PIN and challenge
    TSAuthentication.authenticatePinCode(userId, pinCode, challenge, object : TSAuthCallback<TSPinCodeAuthenticationResult, TSPinCodeAuthenticationError> {
        override fun error(error: TSPinCodeAuthenticationError) {
            // Handle authentication failure
            when (error) {
                is TSPinCodeAuthenticationError.NotRegistered -> {
                    showError("Pin code authenticator is not registered")
                }
                is TSPinCodeAuthenticationError.InternalError -> {
                    showError("Internal error " + error.errorMessage)
                }
            }
        }

        override fun success(result: TSPinCodeAuthenticationResult) {
            // Build the result payload to send back to the journey
            TSIdo.submitClientResponse(
                TSIdoClientResponseOptionType.ClientInput.type,
                PinCodeAuthenticationInput(
                    result.keyId(),
                    result.signature(),
                    userId
                ),
                callback
            )
        }
    })
}