engineering

External User Verification with Forms

Learn how to leverage Auth0 Forms to implement an invitation code workflow and improve the onboarding of your SaaS users.

Some applications require a user to be verified by an external service before they can log in to the system. This can be in the form of an invite code for a free trial in a SaaS application, address verification for a real estate listing website, or maybe something more robust for a healthcare or financial services application.

Auth0 recently released Custom Prompts to help app builders collect more information about their users upon signup. These fields can be captured, and further processing can be done with the Pre Registration Action Flow. Capturing too much information (or capturing information conditionally) can have impacts on the end-user experience. Auth0 has recently released Forms, a new drag-and-drop editor for collecting user information, executing workflows, and orchestrating event data, which can be applied to these verification workflows as well. For this article, we will address the invite code workflow where users must verify their invitation code before being able to log in to the application.

Note: Custom Prompts and Forms work very nicely together as a user who completes a sign-up will then trigger a Login Flow where Forms can be launched.

In this blog post, we will leverage Auth0 Forms to collect this invite code and make an HTTP request to an externally hosted API to verify that the code + email combo is valid. This is one narrow use case, but the solution we are going to outline can handle any scenario where a user must provide input to be verified externally before granting access.

Use Case

Let’s imagine you have a SaaS application that has an upcoming private Beta launch. You do not want to restrict users from signing up to your platform (so you can ingest them into your MarTech stack), but you do not want to allow them to officially log in to the platform unless they have been given access by a member of your team or an affiliate. You could solve this use case in many ways, and in this instance, we will solve this by having users provide an “Invite Code” to verify they have been given access by your backend application. You would send the user’s email alongside this code before approving their access to your private launch. You could collect this code during the signup phase using Auth0 Custom Prompts, but if they failed the validation, their account would not be created. Your goal is to create the accounts and then only gate access to the private beta behind this invite code.

Prompting a User with a Form

We will use a Post-Login Action to launch a user into this flow. They will automatically enter this flow after sign up and any time they come back to your application. This Action will check a flag on the user's profile to see if they have been previously verified or not.

exports.onExecutePostLogin = async (event, api) => {
  if (event.user.app_metadata.verified !== true) {
     const FORM_ID = 'ap_eJWCKKPimBy86AyTRsQikQ';
     api.prompt.render(FORM_ID);
  }
};

You can access the Forms in your Auth0 tenant by navigating to ActionsForms, which will launch the editor in a new tab. Here is the visual editor for our specific Flow:

High-Level Form Flow

High Level Form Flow

The User is prompted to enter their invite code into a text prompt, and then a flow is invoked. In the image below, you can see the flow diagram that checks if the code result has an error, and if so, we will show an error message; otherwise, the flow continues.

Verification Flow

Verification Flow

Here, we can see how to configure the HTTP request step to call your endpoint using the user’s email address and the collected invite code.

HTTP Request Structure

HTTP Request Structure

Based on the response, if the result comes back as true, the user will continue the flow, and if not they will be shown an error message which will prevent them from continuing until they input a correct value.

Conditional Configuration

Conditional Configuration

Once the user enters a correct value, the flow exits and returns to the main path, where it will let them know that access has been granted and they can continue using the application. Our Action that launched the flow must have a continue section to resume the authentication transaction, and we will also save a flag on their profile to prevent them from having to enter this flow again.

exports.onContinuePostLogin = async (event, api) => {
  api.user.setAppMetadata("verified", true);
};

Success Prompt

Success Prompt

And here is the flow end-to-end for a new user who signs up and has a valid email + code combo.

End to End User Journey

Here is the finished JSON for you to begin building your verification flow today! You can import this directly into your tenant by navigating to the Forms window → Create Form → Import It!

Importing Existing Flow with JSON

Importing Existing Flow with JSON

{
  "version": "3.0.0",
  "form": {
    "name": "Invite Code",
    "description": null,
    "messages": {
      "custom": {},
      "errors": {}
    },
    "languages": {
      "default": null,
      "primary": "en"
    },
    "translations": {},
    "start": {
      "nextNode": "step_Y3AO",
      "coordinates": {
        "x": 22,
        "y": 167
      },
      "hiddenFields": []
    },
    "nodes": [
      {
        "id": "step_Y3AO",
        "type": "STEP",
        "alias": "Collect Code",
        "config": {
          "nextNode": "flow_Cilk",
          "components": [
            {
              "id": "rich_text_sME2",
              "type": "RICH_TEXT",
              "config": {
                "content": "<h3><strong>Please enter your invite code.</strong></h3>"
              },
              "category": "BLOCK"
            },
            {
              "id": "invite_code",
              "hint": null,
              "type": "TEXT",
              "label": "Invite Code",
              "config": {
                "maxLength": null,
                "minLength": null,
                "multiline": false,
                "placeholder": "XXX-XXX",
                "defaultValue": null
              },
              "category": "FIELD",
              "required": true,
              "transient": false
            },
            {
              "id": "next_button_xlCe",
              "type": "NEXT_BUTTON",
              "config": {
                "text": "Continue"
              },
              "category": "BLOCK"
            }
          ]
        },
        "coordinates": {
          "x": 323,
          "y": -36
        }
      },
      {
        "id": "flow_Cilk",
        "type": "FLOW",
        "alias": "Verify Code",
        "config": {
          "flowId": "#FLOW-1#",
          "nextNode": "step_IifF"
        },
        "coordinates": {
          "x": 1051,
          "y": 139
        }
      },
      {
        "id": "step_IifF",
        "type": "STEP",
        "alias": "Verification Complete",
        "config": {
          "nextNode": "$ending",
          "components": [
            {
              "id": "rich_text_pMye",
              "type": "RICH_TEXT",
              "config": {
                "content": "<h3><strong>Welcome to Demo0, </strong></h3><h3><strong>Click Continue to get Started!</strong></h3>"
              },
              "category": "BLOCK"
            },
            {
              "id": "next_button_Z6Md",
              "type": "NEXT_BUTTON",
              "config": {
                "text": "Continue"
              },
              "category": "BLOCK"
            }
          ]
        },
        "coordinates": {
          "x": 1510,
          "y": -2
        }
      }
    ],
    "ending": {
      "content": null,
      "redirection": null,
      "callback": null,
      "afterSubmit": {
        "email": null,
        "flowId": null
      },
      "coordinates": {
        "x": 2194,
        "y": 110
      },
      "resumeFlow": true
    },
    "social": [],
    "style": {
      "css": null,
      "theme": "ROUND",
      "version": "MODERN"
    },
    "tags": []
  },
  "flows": {
    "#FLOW-1#": {
      "name": "Verify Invite Code",
      "description": null,
      "actions": [
        {
          "id": "http_request_verify_code",
          "type": "HTTP",
          "alias": "Verify Invite Code",
          "notes": null,
          "action": "SEND_REQUEST",
          "params": {
            "url": "https://edge.samyap.dev/api/verify-code",
            "body": {
              "email": "{{context.user.email}}",
              "invite_code": "{{fields.invite_code}}"
            },
            "type": "JSON",
            "basic": null,
            "method": "POST",
            "params": {},
            "headers": {},
            "acceptNOK": false,
            "connectionId": null
          },
          "allowFailure": false
        },
        {
          "id": "if_then_condition_q0Ym",
          "type": "FLOW",
          "alias": "Verify Code Result",
          "notes": null,
          "action": "BOOLEAN_CONDITION",
          "params": {
            "then": [],
            "evaluate": {
              "operands": [
                {
                  "operands": [
                    "{{ http_request_verify_code.body.result }}",
                    "true"
                  ],
                  "operator": "EQ"
                }
              ],
              "operator": "AND"
            },
            "otherwise": [
              {
                "id": "show_error_message_4rkM",
                "type": "FLOW",
                "alias": null,
                "notes": null,
                "action": "ERROR_MESSAGE",
                "params": {
                  "message": "Your code is invalid.  Please contact support for help"
                },
                "allowFailure": false
              }
            ]
          },
          "allowFailure": false
        }
      ],
      "triggers": {
        "webhook": {
          "secret": null,
          "enabled": false
        }
      },
      "synchronous": true,
      "security": {
        "rateLimits": []
      }
    }
  },
  "connections": {}
}

Conclusion

Today you learned how to leverage Auth0 Forms to gather input from users to verify them with external systems using the no-code Forms and Flows editors. This capability can be extended to numerous applications such as Progressive Profiling, Consent Collection and many more.

At Auth0, we aim to provide a high-quality service and developer experience for our customers. The first interaction with your customers is oftentimes a login screen, so we understand how important it is for the user experience to be top-notch. With our hosted Universal Login, we take away the burden of deploying and maintaining these highly secure workflows. With the capabilities of our forms outlined in this post, we are able to help your teams iterate more quickly and customize the experience to be sticky for your brand and ultimately help your business grow.