Register Passkeys

Registers WebAuthn biometrics for user 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 register passkey credentials for WebAuthn-based biometric authentication. Once registered, users can login using passkeys using the Passkey Authentication step.

Passkey credentials are bound to a specific user, Mosaic application, and domain. The user context may be provided implicitly by the journey if the user is already authenticated; otherwise, a user identifier must be specified in the step configuration. Credentials should only be registered to a user that has verified their identity (e.g., using email OTP, document verification, or externally using a legacy identity provider).

When this step is triggered, it instructs the client to register passkey credentials on the device, and provides the relevant registration input based on the step configuration. This includes the user identifier and other WebAuthn-specific configuration. The client then uses WebAuthn SDK calls to perform the client-side registration, where the step configuration may be used as input to the registration call.

Once credentials are registered on the device, the SDK returns the WebAuthn encoded result. The client sends this result to the journey (using the IDO SDK call), and the step completes the registration for the user in the Mosaic platform. If successful, the journey proceeds to the next step. If it fails or is cancelled, the journey is either aborted or proceeds to the dedicated cancel/failure branches (if configured).

To support passkey registration, you'll need to:

  • Configure the Passkey login method for your application
  • Associate your domain with the passkey credentials (for mobile apps only)
  • Implement the WebAuthn SDK calls required for registration
  • Configure the Register Passkeys journey step
  • Implement the IDO SDK call that submits the client response

For more, see the WebAuthn quickstarts (skip the "Complete registration" section) and see the example below

Configuration

Field Description
User Auth State Indicates if the user has authenticated in this journey. If the user is authenticated (default), the user context is provided implicitly by the journey. If not, a user identifier must be configured.
User Identifier User identifier, which is only configured if the journey doesn't authenticate the user before invoking this action.
Allow Cross Platform If to allow registration using cross-platform authenticators, such as a USB security key or a different device. If allowed, cross-device authentication flows can be performed using the native browser experience (via QR code). Default is Yes.
Register Discoverable Must be set to Yes to register credentials as passkeys when supported (except for Apple devices, which always register credentials as passkeys). Default is Yes.
Display Name Human-palatable name for the user account, only for display (max 64 characters). If not set, the external user ID of the verified user will be used instead.
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

Consider a journey that authenticates the user (e.g., using password) before initiating a passkey registration. In our example, the User Auth State is set to The user is already authenticated in the step configuration.

When executed, this step returns to the client the idoServiceResponse object. It will have the journeyStepId set to WebAuthnRegistration and will include the user identifier and other WebAuthn configuration in the data. In the example below, this data is used as input to the WebAuthn registration call. Once registration is completed, the client submits the encoded result back to the journey to complete the step.

JavaScriptKotlinSwift
Copy
Copied
let data = idoServiceResponse.data;
// Registers passkey credentials on the device using data provided by the journey
let registrationResult = window.tsPlatform.webauthn.register(
    data.username,
    {
        allowCrossPlatformAuthenticators: data.allow_cross_platform_authenticators,
        registerAsDiscoverable: data.register_as_discoverable,
        displayName: data.display_name
    }
);
// Submits to the journey the encoded result returned by the WebAuthn SDK call
let nextIdoServiceResponse = await window.tsPlatform.ido.submitClientResponse(
    ClientResponseOptionType.ClientInput,
    {
        "webauthn_encoded_result": registrationResult
    })
Copy
Copied
data class WebAuthnRegisterResult(val webauthn_encoded_result: String)
// Expected structure of the data:
// {
//   "webauthn_encoded_result": "passkey registration result",
// }

// ...

fun registerPasskeys(idoServiceResponse: TSIdoServiceResponse) {
    val userName = (idoServiceResponse.data as JSONObject).optString("username")
    val displayName = (idoServiceResponse.data as JSONObject).optString("display_name")

    if (!userName.isNullOrEmpty()) {
        activity?.let {
            TSAuthentication.registerWebAuthn(it, userName, displayName, object: TSAuthCallback<RegistrationResult, TSWebAuthnRegistrationError> {
                override fun error(error: TSWebAuthnRegistrationError) {
                    // Handle error
                }

                override fun success(registrationResult: RegistrationResult) {
                    // Submits to the journey the encoded result returned by the WebAuthn SDK call
                    submitResponse(TSIdoClientResponseOptionType.ClientInput.type, WebAuthnRegisterResult(registrationResult.result()))
                }
            })
        }
    }
}
Copy
Copied
TSAuthentication.shared.registerWebAuthn(username: "[USERNAME]", displayName: "[DISPAYNAME]") { result in
    switch result {
    // Submits to the journey the encoded result returned by the WebAuthn SDK call    
    case .success(let response):
        TSIdo.submitClientResponse(clientResponseOptionId: .clientInput,
                                   data: ["webauthn_encoded_result": response.result])
    // Handle error
    case .failure(let error):
        TSIdo.submitClientResponse(clientResponseOptionId: .fail)
        debugPrint("[DEBUG] Error: \(error)")
    }
}