Eliminating Spam in Modx Forms

I've been trying to find the optimal system for reducing the number of fake leads being generated in Salesforce.

Salesforce has a useful tool for generating a Web-To-Lead form which was the template for what had been used on the site so far. There were also a bunch of inline script for form validation which would prevent the form from submitting if fields were not completed (using event.preventDefault()).

I looked into Google's reCaptcha, which, with the latest v3, blocks spam submissions without adding any annoying test for the user.

Image result for recaptcha
reCaptcha v3 eliminates the reCaptcha v2 element that requires users to tick a box before submission

So, the goal is to create a Modx form that submits to Salesforce and supports reCaptcha v3. This turned into a three step process:

1. Using FormIt for form validation

FormIt was already installed and in use in other places on the site. FormIt provides a simpler way to validate forms, rather than having the write JavaScript to check each field. So I went from something like this:

<form><input id="name" type="text" name="name" placeholder="Name"/>
<input id="email" type="text" name="email" placeholder="Email"/>
<input type="submit" value="Submit" />
</form>
<script>
function validate(evt){
var ok = true;
var name = document.getElementById("name");
if (name.value.length === 0){
ok = false
}
//other checks
if (!ok){
evt.preventDefault();
return false;
}
}
</script>



To this:

[[!FormIt? &validate=`name:required, email:required`]]
<form>
<input id="name" type="text" name="name" placeholder="Name"/>
<input id="email" type="text" name="email" placeholder="Email"/>
<input type="submit" value="Submit" />
[[!+fi.validation_error_message]]
</form>


Much better to get rid of all that cumbersome javascript.

2. Posting to Salesforce

I wish I could take credit for this but I know nothing about writing Modx snippets. I found this snippet already saved conveniently called SalesforceSubmit.

All that is required is to add it as a custom hook to the FormIt call:

[[!FormIt? &hooks=`SalesForceSubmit, redirect` &redirectTo=`378` &validate=`last_name:required, email:required`]]

The form will post to Salesforce and then redirect to a confirmation page.


3. Incorporating reCaptcha v3

It took me a while to get this all figured out in my head and working on screen. The first step was to set up reCaptcha and put the script on the page. Add the script to the header tag:

<script src='https://www.google.com/recaptcha/api.js?render=PUBLIC KEY'></script>

Add an additional hidden input to the form.

<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">

Add the following script:

<script>
grecaptcha.ready(function() {
grecaptcha.execute('PUBLIC KEY, {action: 'test_form'})
.then(function(token) {
document.getElementById('g-recaptcha-response').value = token;
});
});
</script>

Then I needed to create a custom snippet called "recpatchav3" and call this in my Formit hooks

[[!FormIt? &hooks=`recaptchav3, SalesForceSubmit, redirect` ... ]]

Place it before the other hooks so that it is called first.

I ended up using the php code inside the snippet as given by Jonathan Stark, modifying the code to return a false value if the result was not a success and true otherwise.

In order to test that this was working, I used Chrome's dev tools to emulate as a bot.
I found that on submitting the form I was always redirected, i.e. the captcha always passed. However, when I manually search to 

https://www.google.com/recaptcha/api/siteverify.....

(with relevant secret and token), the success was true but the score low (0.1) when emulating as a bot.

To assist with this, I add a second condition to determine if the hook should pass as well as log the score in the 'ratings' field of the Salesforce lead: 


if ((!$result->success)||$score < 0.2) {
    return false;
}
else {
   $hook->setValue('rating',$score);
   return true;

So the final snippet looks like this:

$post_data = http_build_query(
array(
'secret' => 'PRIVATE KEY,
'response' => $_POST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR']
)
);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $post_data
)
);
$context = stream_context_create($opts);
$response = file_get_contents('https://www.google.com/recaptcha/api/siteverify', false, $context);
$result = json_decode($response);
$score = $result->score;
$errorMsg = 'There was a problem with your recaptcha verification.';
if ((!$result->success)||$score < 0.2) {
$hook->addError('error_message',$errorMsg);
return false;
}
else {
$hook->setValue('rating',$score);
return true;
}

Comments

Popular posts from this blog

Symlinks and Static Resources

Effective Form Design