Integrate a custom captcha service with Customer Insights - Journeys forms

Customer Insights - Journeys forms allow you to use custom captcha bot protection to validate form submissions. This article gives an example of how to integrate Google reCAPTCHA. The flow is similar for other captcha services.

Note

In the current app version, only one captcha implementation can be active. If you use your own captcha provider (as outlined below), existing forms that use the out-of-the-box captcha will stop working. A custom captcha implementation requires at least basic knowledge of writing and debugging dataverse plugins.

The process consists of these steps:

  1. Add reCAPTCHA to the form.
  2. Add the captcha text value to the form submission once the form is submitted.
  3. Create a plugin in the CRM that validates the captcha.

Step-by-step example: Integrate Google reCAPTCHA

1. Add reCAPTCHA to the form

  1. Create a form in the Customer Insights - Journeys form editor.

  2. Add a data-validate-submission="true" attribute to the <form> element, which enables custom validation on the form submission:

    Add attribute to form element.

  3. Add a <div id="g-recaptcha"> in the form as placeholder for reCAPTCHA. This div ID is used as a reference later. It's recommended to put the placeholder between the last field and submit button.

    Add placeholder for reCAPTCHA.

  4. Publish the form and embed the form into your website.

  5. Edit the page where the form was embedded. Add the script provided by Google into the page header. This script loads the reCAPTCHA with the onLoad callback parameter. This callback is called as soon as the captcha is loaded.

    <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback" async defer></script>
    
  6. Add the onLoadCallback function:

    function onloadCallback() {
        grecaptcha.render('g-recaptcha',
        { 
          sitekey: '{sitekey}',
        });
    }
    

    Replace the {sitekey} placeholder with the one provided by Google. This callback function renders the reCAPTCHA inside the placeholder <div id="g-recaptcha"> you created earlier.

  7. Register the onloadCallback function to be called by the form loader:

document.addEventListener("d365mkt-afterformload", onloadCallback);

2. Add the captcha text value to the form submission

Once the form is submitted, the g-recaptcha-response parameter is added automatically to the form submission. In the next steps, you'll build a plugin that hides this value, as it will be added to the ValidationOnlyFields list in the response object returned by the plugin code.

G-recaptcha-response parameter is added.

3. Create a plugin

3.1 Create a Visual Studio Project for the plugin

  1. Open Visual Studio and create a new Class Library project using .NET Framework 4.6.2.
  2. In Solution Explorer, select Manage NuGet Packages and install Microsoft.CrmSdk.CoreAssemblies.

3.2 Create the plugin class

  1. Rename Class1.cs to CustomValidationPlugin.cs.

  2. Make the CustomValidationPlugin class inherit from the IPlugin interface and add the Execute method.

    public class CustomValidationPlugin : IPlugin
    {
       public void Execute(IServiceProvider serviceProvider)
       {
    
       }
    }
    
  3. Add the following code into the execute method to retrieve context and tracing service.

    public void Execute(IServiceProvider serviceProvider)
    {
       // get tracing service
       ITracingService tracingService =
       (ITracingService)serviceProvider.GetService(typeof(ITracingService));
    
       // get plugin execution context
       IPluginExecutionContext context = (IPluginExecutionContext)
       serviceProvider.GetService(typeof(IPluginExecutionContext));
    }
    
  4. Add this code to retrieve the form submission parameter string. It's a JSON encoded string representing the fields that the user submitted in the form. This process retrieves this string and deserializes it using a Deserialize helper method and FormSubmissionRequest class that is defined later. This checks that the Fields array contains a key for g-recaptcha-response. If the reCAPTCHA key isn't found, it returns skipping validation as the form that's processing didn't contain a Google recaptcha element.

    var requestString = (string)context.InputParameters["msdynmkt_formsubmissionrequest"];
    var requestObject = Deserialize<FormSubmissionRequest>(requestString);
    if (!requestObject.Fields.TryGetValue("g-recaptcha-response", out string recaptchaToken))
    {
       tracingService.Trace("g-recaptcha-response was not present in form submission");
       return;
    }
    
  5. Add the following code to return if g-recaptcha-token value is null or empty.

    if (String.IsNullOrEmpty(recaptchaToken))
    {
       tracingService.Trace($"g-recaptcha-response value not found");
       return;
    }
    
  6. Add the following code to validate the Google captcha token against Google APIs.

    string url = "https://www.google.com/recaptcha/api/siteverify";
    using (HttpClient client = new HttpClient())
    {
       var content = new FormUrlEncodedContent(new Dictionary<string, string>
       {
          {"secret", "your_secret_key"},
          {"response", recaptchaToken}
       });
    
       try
       {
          var response = client.PostAsync(url, content).Result;
          if (!response.IsSuccessStatusCode)
          {
             tracingService.Trace($"Request Failed: ({response.StatusCode}){response.Content}");
             return;
          }
    
          var responseString = response.Content.ReadAsStringAsync().Result;
    
          gRecaptchaResponse = Deserialize<GRecaptchaResponse>(responseString);
    
          var resp = new ValidateFormSubmissionResponse()
          {
             IsValid = isValid,
             ValidationOnlyFields = new List<string>() { "g-recaptcha-response" }
          };
          context.OutputParameters["msdynmkt_validationresponse"] = Serialize(resp);
        }
        catch (Exception e)
        {
           tracingService.Trace($"{e.Message}");
        }
    }
    

    First, the URL is defined, then an instance of HttpClient is created. A FormUrlEncodedContent object is created containing the recaptchaToken retrieved in previous steps and the secret key that is provided by Google. Then a POST request is sent and the status code is checked, if not successful it returns. If successful, it deserializes the response using the Deserialize helper method and GRecaptchaResponse that's defined later. It then creates a new ValidateFormSubmissionResponse object, serializes it, and sets it as the value of the output parameter msdynmkt_validationresponse, which is the one Microsoft service it uses to accept or reject the submission. Adding the g-recaptcha-response string to the ValidationOnlyFields list hides this field from the form submission in the UI.

  7. Add the following code to define Serialize and Deserialize helper methods.

    private T Deserialize<T>(string jsonString)
    {
       serializer = new DataContractJsonSerializer(typeof(T));
       T result;
       using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
       {
          result = (T)serializer.ReadObject(stream);
       }
       return result;
    }
    
    private string Serialize<T>(T obj)
    {
        string result;
        serializer = new DataContractJsonSerializer(typeof(T));
        using (MemoryStream memoryStream = new MemoryStream())
        {
           serializer.WriteObject(memoryStream, obj);
           result = Encoding.Default.GetString(memoryStream.ToArray());
        }
        return result;
    }
    
  8. Add the following code to define the classes needed to Serialize/Deserialize JSON strings objects.

    public class FormSubmissionRequest
     {
         public Dictionary<string, string> Fields { get; set; }
     }
    
     public class GRecaptchaResponse
     {
         public bool success { get; set; }
     }
    
     public class ValidateFormSubmissionResponse
     {
         public bool IsValid { get; set; }
         public List<string> ValidationOnlyFields { get; set; }
     }
    

3.3 Sign and build the plugin

  1. Right click on the project and select Properties in the Solution Explorer.
  2. Select the Signing tab and check the Sign the assembly checkbox.
  3. Select <New...>.
  4. Enter a key file name and deselect Protect my key file with a password.
  5. Build the project.
  6. You can find the plugin assembly CustomValidationPlugin.dll in \bin\Debug.

3.4 Register plugin

  1. Open PluginRegistration.exe.
  2. Select Create new connection.
  3. Choose Office 365.
  4. Select Login.
  5. Select Register and then Register new assembly.

    Select Register and then Register new assembly.

  6. Select the (...) button in step 1 and select the dll built in previous steps.
  7. Select Register selected plugin.

3.4 Register Step

  1. Select CustomValidationPlugin from the list of the registered assemblies.
  2. Select Register New Step.
  3. Enter msdynmkt_validateformsubmission into Message text field.
  4. Make sure Execution Mode is set as Synchronous.

    Make sure Execution Mode is set as Synchonous.

  5. Make sure Execution order is set to 10.
  6. Make sure Event Pipeline Stage Of Execution is set as Post Operation.
  7. Select Register New Step.

Conclusion

When a form with the data-validate-submission attribute is submitted, your custom plugin runs and validates the reCAPTCHA response with Google services. The custom plugin will run after the default Microsoft validation plugin. If there are no Microsoft captcha fields in the form, the Microsoft plugin sets IsValid:false and the submission fails unless you overwrite it with IsValid:true.

Validation flow.