Email OTP Authentication
Authenticates the user using a one-time code (OTP) sent by email
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 sends a one-time code (OTP) to the user's email, and then validates the code to authenticate the user. For example, it can be used to validate the user's email, authenticate the user in recovery flows, or as a second-factor for authentication.
This step requires the email to send the code to, and the user identifier if it's different than the email. If the email is the user identifier, it can be simply obtained using a login form (see Login Form). If different, the journey must also ensure that the email indeed corresponds to the authenticating user. For example, if a form collects both the identifier and email, the journey should check that it's a known email for this user. Or if the form collects only the identifier, the journey can use it to look up the email from a trusted source.
Once initiated, this step sends an OTP to the specified email and then asks the client to send it back to the journey. The client retrieves the code from the user and submits it to the journey for validation. If successful, the journey sets the user context to the authenticated user and continues to the next step. Tokens generated for the authentication can be accessed in subsequent steps using @policy.userTokens()
.
When building your authentication solution, you'll need to consider how to handle the different error cases. If the user didn't receive the code, the client can ask the journey to resend it (using a Resend response). If the user submits an invalid code, the journey will ask the client to resubmit the code (errorData will contain the error). And if the step fails, the journey proceeds to a failure branch (if specified); otherwise, the journey is aborted and an error is sent to the client.
The OTP settings for your application include security features like code length, expiration, and failure lockout rules.
Configuration
Field | Description |
---|---|
The email is also the username | Indicates whether the user can be identified by the email. If the email is different than the username (default), a user identifier must be configured. |
User Identifier | User identifier, specified using an expression. Must be configured only if the username is different than the email that receives the code. |
Email to send the code to, specified using an expression. | |
Error Output Variable | Name of the variable that stores any errors returned by 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 when a custom branch is selected, 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
Suppose a login form is used to collect the user's email, which is also the user identifier in our example. The form ID is loginForm
, the input will be stored in a variable named loginData
, and only Email OTP is enabled. The OTP branch has the default branch ID (email_otp
) and schema is updated to expect only email (email
).
The authentication step obtains the email from the form output (loginData.email
):
When executed, this step sends an OTP to the specified email and then instructs the client to send it back once the user inputs it. The idoServiceResponse
object will include the journeyStepId
as EmailOTPAuthentication and the length of the code in the data
. For example:
{
"data": {
"code_length": <integer_code_length>
}
}
Once the client obtains the code, it submits it back to the journey to complete the authentication. For example:
// Submits the OTP entered by the user
window.tsPlatform.ido.submitClientResponse(
ClientResponseOptionType.ClientInput,
{
"passcode": "<passcode>"
})
data class OtpResult(val passcode: String)
// ...
val responseData = OtpResult(collectedOTP)
// Submits the OTP entered by the user
TSIdo.submitClientResponse(
TSIdoClientResponseOptionType.ClientInput.type,
responseData,
callback
)
// Submits the OTP entered by the user
TSIdo.submitClientResponse(
clientResponseOptionId: .clientInput,
data: ["passcode": "[passcode]"]
)
In case the client has a button for resending the OTP code, it can submit a resend request to the journey. For example:
// Request resend clicked by the user
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.Resend)
// Request resend clicked by the user
TSIdo.submitClientResponse(
TSIdoClientResponseOptionType.Resend.type,
null,
callback
)
// Request resend clicked by the user
TSIdo.submitClientResponse(clientResponseOptionId: .resend)