Add reCAPTCHA to Newsletter Signup

One of the new features in the Vinson release of SCA was functionality that makes it enables shoppers to sign up for newsletters. With these gathered email addresses, you can feed them into a marketing service such as MailChimp, Campaign Monitor and Bronto.

However, one issue you can face when offering a signup form like this — especially to logged out users — is spam. What you do not want are bots submitting email addresses. In this article, we're going to look at implementing Google's reCAPTCHA service on the signup form.

How reCAPTCHA Works

Despite the heading, I can't say exactly how it works. In recent years, these sorts of verification services have moved away from asking you to decipher two words to simply asking you to click a checkbox.

This new method is interesting because it's meant to be easier for humans and harder for bots, but how does it work?

Reading the analyses of various people who have studied it (and what Google themselves say), there seems to be a consensus that it now takes into account things like:

  • Evidence about the user before they attempt to use the form, such as prior browsing habits and activity attached to their IP address
  • Evidence about the user as they complete the form, such as how long it takes them to fill out fields and how the mouse cursor moves
  • Evidence about the user after they complete the form, to constantly build up a profile on the user

This is equal parts interesting and scary. It seems that Google is tracking us constantly to see if we're still humans! It's part of their 'risk algorithm' that scores users on their behavior, and then decides how to treat them. If a user raises suspicions, then they'll face a test — normally this'll ask them to select objects of a certain kind from a list, for example:

Still, reCAPTCHA is a useful way of cutting down spambots. While there might be a few that get through, you'll ensure the vast majority don't. And with legitimate users only having to click a simple checkbox, this is a far superior user experience to older captcha methods.

However, one thing that all captcha methods share is that they are forms of challenge-response authentication. In other words, this means that one party asks a question and the other answers, and then the first party verifies the answer. Entering a username and password combination to log into an account is another example of this.

Visualized, the system is going to look like this:

  1. The user completes the captcha, which is sent to Google and includes the server key (identifying the origin of the request)
  2. The user submits the signup form, which is sent to the NetSuite backend
  3. The NetSuite backend contacts Google to ask if the CAPTCHA was solved correctly, and Google responds

Note that the captcha widget loads after calling a JavaScript file from Google — the user will, therefore, need JavaScript enabled to use the form (but this is a requirement for the site anyway).

Getting this functionality to work takes three steps:

  1. Signing up with Google's service and doing basic prep work
  2. Implementing the frontend widget
  3. Verifying the user's response in the backend

We're going to build this functionality as an extension of the Newsletter module, but you could build this is to be more generic and call it in different places. For example, any form that can be submitted without the user first logging in is a potential threat to bots, such as a form used to contact your organization.

And with that in mind, let's take a look at these steps.

Sign Up for reCAPTCHA

First things first: you're going to need to go to the Google reCAPTCHA site and sign up for an account (make sure you're signed in with a work account, and not a personal one!). When you do, they give you two keys: a site key and a secret key. The site key is used to identify the site and is sent to Google when the user submits a captcha. The secret key (which you shouldn't reveal publicly) is used to verify the communication between your site and Google. Keep the window open or make a note of them, we're going to use them shortly.

Prepare the Module Structure

As I said, we're going to build this functionality as an extension to the Newsletter module and, in line with our guidelines on customizing and extending the code, we're going to create a new module. In your folder for customizations, create a folder structure as follows:

  • Newsletter.Extended@1.0.0
    • JavaScript
    • Sass
    • SuiteScript
    • Templates

You'll also need to create an ns.package.json file for the module. In it, put the following:

{
    "gulp": {
        "javascript": [
            "JavaScript/*.js"
        ],
        "templates": [
            "Templates/*"
        ],
        "sass": [
            "Sass/*.scss"
        ],
        "ssp-libraries": [
            "SuiteScript/*.js"
        ]
    },
    "overrides": {
        "suitecommerce/Newsletter@2.0.0/Templates/newsletter.tpl": "Templates/newsletter.tpl"
    }
}

You'll note the one override: we need to make modifications to the template that renders the newsletter signup box, and templates cannot be extended.

Extend the View

The view is what will render the reCAPTCHA widget. It's going to extend the main view used by the newsletter as well as use its model.

In the JavaScript folder, create Newsletter.Extended.View.js and put the following code in it:

define('Newsletter.Extended.View'
, [
    'Newsletter.View'
  , 'Newsletter.Model'
  , 'Backbone'
  , 'underscore'
  , 'jQuery'
  , 'Utils'
  ]
, function (
    NewsletterView
  , NewsletterModel
  , Backbone
  , _
  )
{
  'use strict';

  // @extend Newsletter.View to add the captcha to Newsletter feature
  _.extend(NewsletterView.prototype, {

    // @method initialize Overrides Newsletter.View.initialize method with our own re-captcha logic
    initialize: _.wrap(NewsletterView.prototype.initialize, function wrapNewsletterInitialize (fn, options) {
      fn.apply(this, _.toArray(arguments).slice(1));
      window.renderCaptcha = _.bind(this.render, this);
      var url = 'https://www.google.com/recaptcha/api.js?onload=renderCaptcha&render=explicit';
      this.captchaPromise = jQuery.getScript(url);
    })

    // @method render Overrides Newsletter.View.render method with our own
  , render: function render ()
    {
      if (this.captchaPromise.state() === 'resolved')
      {
        Backbone.View.prototype.render.apply(this, arguments);

        try
        {
          grecaptcha.render(
            this.$el.find('#newsletter-grecaptcha')[0]
          , {
              'sitekey': '---YOUR---SITE---KEY---HERE---'
            }
          );
        }

        catch(err)
        {
          console.log('Error rendering recaptcha: ', err);
        }
      }
    }
  });

  _.extend(NewsletterView.prototype.feedback, {
    'NO_CAPTCHA' : {
      'type': 'error'
    , 'message': _('You must complete the captcha').translate()
    }
  });
});

There's a fair bit going on here. Firstly, we need to extend the newsletter view. If you remember from my tutorial on extending JavaScript, one way to add your code is to wrap an existing function and then pass your code into it — this is what we're doing in the initialize function. Specifically, we are including code that calls the reCAPTCHA API and requests the script to load the widget. We create a promise that resolves when the script has finished.

Next is the render method that we just bound in the initialize method. This renders the widget, and the first thing it does is check whether the promise we just made has been resolved. When it has, it's going to run the grecaptcha.render method. This is a method included in the reCAPTCHA JavaScript file that we're calling, and you can (and should!) read about it in the Google reCAPTCHA docs. Here, we are explicitly telling it to render in the element of our choice (which we haven't created yet) and passing it our site key. Make sure you replace ---YOUR---SITE---KEY---HERE--- with your actual site key (not your secret key!).

Right, so after that we need to put in some SuiteScript.

Extend the Service Controller

This part of the guide assumes you're using Vinson and the new service controllers — if you're not, you'll need to make adjustments to make it fit in with the old services.

What we're going to do is extend and wrap the service's POST method so that it has code to handle the reCAPTCHA requests and responses to and from Google. In the SuiteScript folder, create Newsletter.Extended.ServiceController.js and then put the following in it:

// Newsletter.Extended.ServiceController.js
// We are extending the Newsletter service controller in order to use the captcha
define('Newsletter.Extended.ServiceController',
  [
    'Application'
  , 'ServiceController'
  , 'Newsletter.ServiceController'
  , 'Newsletter.Model'
  , 'underscore'
  ]
, function(
    Application
  , ServiceController
  , NewsletterServiceController
  , NewsletterModel
  , _
  )
{
  'use strict';

  _.extend(NewsletterServiceController, {
    post: _.wrap(NewsletterServiceController.post, function wrapNewsletterPost (fn)
    {
      var captcha = this.data['g-recaptcha-response'] || '';

      if (!captcha)
      {
        throw NewsletterModel.buildErrorAnswer('NO_CAPTCHA');
      }

      var url = 'https://www.google.com/recaptcha/api/siteverify'
      , secret = '---YOUR---SECRET---KEY---HERE---'
      , response = nlapiRequestURL(url + '?secret=' + secret + '&response=' + captcha)
      , body = JSON.parse(response.getBody());

      if (!body.success)
      {
        throw NewsletterModel.buildErrorAnswer('ERROR');
      }

      return fn.apply(this, _.toArray(arguments).slice(1));
    })
  });
});

So, as I said, we're extending the service controller and wrap the post method so that it includes our own code. The first thing we're doing is checking to see if we've got the user's response from the reCAPTCHA challenge request. Check out the reCAPTCHA docs for more detail on this.

Then, we're building the response request, which comprises:

  1. reCAPTCHA's URL for requesting verifications
  2. Your secret key (replace ---YOUR---SECRET---KEY---HERE--- with yours)
  3. The final request URL, which the code executes on the server

And that's it, really, for the code that handles sending and receiving the data. Now it's time to get the widget to appear in the template.

Override the Template

In the ns.package.json file we overrode the template for the newsletter signup box. Now we need to create the template that includes the element where the reCAPTCHA will go.

Now, as we're going to be overriding the original template, we should use the original as the basis for the new one. Go to Modules > Newsletter > Templates and copy newsletter.tpl and paste it into the Templates directory of the customized module. What we need to do is add one line to it — the element where the widget will be loaded into. Typically, captchas should be after the form but before the submit button. However, the default design for the signup box makes this difficult to achieve so I'm just going put it before the form for the timebeing. In the copied template, put the following:

<div id="newsletter-grecaptcha" class="newsletter-grecaptcha"></div>

The important thing is that the id matches the selector we specified in the render function of the view — this is where the code widget will be loaded into.

Add Some Styling

Next, optionally, we can add some styling. Unfortunately, because the widget is loaded directly from Google, there really isn't much styling we can apply to it. Create _newsletter.scss in Sass and put the following in it:

.newsletter-grecaptcha {
    margin-bottom: $sc-small-margin;
    @media (min-width: $screen-sm-min) {
        margin-bottom: $sc-base-margin * 6;
    }
}

All this is doing is adding a bit of padding so that it fits better in the container, which is more or less all you can do.

Update distro.json

The final bit is to include the new module as part of the build process. Open up your distro.json and:

  1. Add the new module to the modules object
  2. Add the new view to the dependencies array for shopping.js
  3. Add the new service controller to the dependencies array for ssp-libraries
  4. Add the new module to the dependencies array for shopping.css

And that's it. Deploy and test!

Deploy and Test

You should see the captcha appear above the signup box.

Now do some testing — click it, does it work? Test the validation out: add an email address and try to subscribe without completing the captcha, are you prevented from doing so? After completing both the form and the captcha, is the record generated in the backend?

One thing you can do is do the captcha and then check the textarea field where the code is saved to. Using your browser's developer tools, inspect the area around the captcha and find the element with an of g-recaptcha-response. Adjust the element's styling to make it visible in the page, and you should see a very long alphanumeric code appear on the page.

Final Thoughts

So we just added a captcha to the newsletter signup form, using an extension module to the newsletter functionality. This tutorial is a good example of how to add customizations to a specific module, and one that is relevant to Vinson in that it extends the newsletter functionality and makes use of service controllers.

A captcha module has applications outside of the newsletter functionality, and could be useful to any form that does not the user to be logged in for. For example, you could add it to a page that allows customers to email you or perhaps on your new user signup form, if you're getting a lot of fake users.

More Information