Guides & tutorials

Building a risk-based and MFA login with your API

No items found.
Table of contents

Security and UX are no longer at odds with each other on user authentication flows. Adaptive forms have begun to be used in combination with user authentication and risk scoring systems to create more secure and frictionless flows. 

In this post, we show you how to build a risk-based and multi-factor authentication login flow with Arengu and your own API, that is compatible with any tech stack.

Previous requirements: What do we need?

To build this use case in a few minutes you just need:

  • Your own API. Connection data and access to the API to which users will log in.
  • Arengu. To create the form, integrate external services and set up the logic.
  • An IP risk score provider. We are going to use the freemium IPQualityScore service to measure the risk of each user, but you can use any other service with an API.

What are we going to build?

A two-step form, in which the step 2 will only be displayed if the user is considered risky. 

The first step of the form will be connected to a flow that evaluates the user's risk level and, if it is considered risky, it will generate and send a temporary code by email, which will be requested and verified in the second step. If not, it will directly log the user in. 

Let's check how to set it up with Arengu!

Build the 2-step form

Go to the form editor and create a two-step form from scratch or select the login form template. Both options are valid. It must include the following steps and fields:

  • Step 1: an email field with "email" as the field ID and a password field with "password" as the field ID too. Mark them both as required.
  • Step 2: a number field e with "otp-code"as the field IDs, marked as required too.

Then, activate the IP collection checkbox in the Settings tab and go to the Flows tab to create a new flow for each form step, by clicking on the corresponding + button. If you have used the login form template, you can delete or edit the flows associated by default.

Once you have created the form and the flows, publish the changes.

Flow 1: Login and IP risk score verification

To make form step 1 work properly, we need to create and build a flow with this structure:

This flow will perform the following operations: 

  1. Connect to our API to check if the credentials are valid.
  2. If user credentials are valid, it will measure the risk of the user using the IPQualityScore service.
  3. Based on the risk score, it will directly log the user in or generate and send a temporary code to use it as a second authentication factor.

1. Login HTTP request

The first action in this flow is an HTTP request, which will check if the user is registered and if the credentials are valid. The settings for this action will vary according to your API but, in our case, they would be:

  • ID: change the ID to "login" to simplify it.
  • Method: choose the POST option.
  • URL: we will use https://custom.demos.arengu.com/login.
  • Headers: if needed, add your Authorization headers.
  • Body:

<pre class="code">

{

  "sub": "{{input.body.email}}",

  "auth_type": "password",

  "password": "{{input.body.password}}"

}

</pre>

2. Unsuccessful login branch

Then, include an If/then condition action to check the response of the HTTP request on the credentials and build two branches.

The settings of this If/then condition action are as follows:

  • Condition: reference the {{login.success}} variable in the first input and choose is true as the evaluation criteria.

For the responses that have not been successful, include an Input value mapping action and Show error message actions in the False branch.

The Input value mapping action will allow you to list the API error codes and configure a different message for each of them, as you can see in the image. To set up this action:

  • Input value: reference the {{login.body.error_code}} variable.
  • Mapping table: reference the output error codes from the HTTP Request action on the first input and write the proper message for the user on the second one.
  • Default output value: you can include here a default error message to be displayed on the form in the event of an error not included in the list.

To display these error messages in the form, simply reference the {{inputValueMapping_login.result}} variable in the Show error message action.

3. IP risk scoring

If credentials are valid, we will make a GET HTTP request to the risk scoring service. The settings for this action would be:

  • ID: change the ID to "getIpScore" for simplicity.
  • URL: copy the JSON example request URL from the IPQualityScore documentation and include in it the user’s IP variable and the API Key of your account in this service. 
  • Method: choose the GET option.

The user's IP variable will appear in the variable selector if you have activated the IP collection option in the form settings and if you have executed this flow at least once. If you have not done this, go back to the form edition page, activate the IP collection option in the Settings tab, publish the change, click on the Preview button and execute the form.

4. Submit the form, for non-risky users

As we did before, include an If/then condition action after the HTTP request to manage its response and create two branches. The settings of this action are as follows:

  • Condition: reference the {{getIpScore.body.fraud_score}} variable in the first input, choose “is less or equal than” as the evaluation criteria and the risk value. For example, we consider users who receive 70 or more points to be risky.

If the score is below 70 points, we will log the user in using a Submit the form action with these settings:

  • Redirect URL: activate this option and reference the {{login.body.claim_url}} variable from the login HTTP request action output.

5. Store state variable, for risky users

If the user's score is considered risky, we will ask for the second authentication factor but, first, we must save authentication data to recall it in the next flow. For this, add a Store state variable action with this settings:

  • Data fields: write the name of the variable “claim_URL in the first input and reference the {{login.body.claim_url}} variable in the second one. You can get this variable from the variable selector, after an execution of the login HTTP request.

6.Generate and send an OTP

Once the authentication data is stored, include a Generate one-time password action and reference the {{input.body.email}} variable from the form in it. 

Then include one of the messaging native actions to send the OTP by email or sms. In this case, we will send it by email with Mailjet.

To set up this action:

  • API Key & Secret key: just copy and paste the API key and the Secret key of your Mailjet account or the messaging service that you have chosen. 
  • Sender email: write the email sender address.
  • Recipient email: reference the {{input.body.email}} variable.
  • Subject: write a subject for the message, you can include the {{generateOtp.code}} variable on it.
  • Message: write the email message and include the {{generateOtp.code}} variable from the previous action. You can use HTML, CSS and your own templates. 

Finally, include a Go to the next form step action and publish all the changes.

Flow 2: Verify the OTP and log in (or not)

To make form Step 2 work properly, we need to create and build a flow with the structure that you can see in the image below, which will perform the following operations: 

  • Verify the one-time password input by the user.
  • If the OTP is correct, log the user in.
  • If the OTP is not correct, display an error message on the form.

This flow will first include a Verify one-time password action with these settings:

  • Reference value: include the {{input.body.email}} variable from the form.
  • Code value: reference the {{input.body.otp-code}} variable from the first flow.

Next, include an If/then condition action to handle the responses of the verification and create two branches with this condition:

  • Condition: reference the {{verifyOneTimePassword.valid}} variable from the output of the previous action and choose “is true” as criteria.

We will also add a Show error message action to display a custom message in the form if the OTP is not correct, in the False branch.

In the True branch, we will configure the Submit the form action, enabling the Redirect to URL option and referencing the {{state.claim_url}} variable.

Just remember that, to recall this variable from the login HTTP request in this flow, we must have saved it in the previous one using the Store state variable action. Then publish all the changes and go back to the form edition page to open the preview and test it.

Test and debug your flows

After testing your form and linked flows, you can see if everything has worked correctly in the Executions tab of each of the flows. Here you can check the inputs, outputs and errors of each of the actions in each execution to fix any possible bugs.

Do you want to try it by yourself? Sign up free or book a demo with our team. Still not sure about it? Take a look at the most common use cases.

You might like to read

Getting started with Arengu

Arengu allows you to build all your user flows connected to your current stack, and avoids coding all the UI, complex integrations, validations or logic from scratch. Try it for free and start building faster and scaling your application needs as they grow.