Home CAPTCHA
CAPTCHA
Cancel

CAPTCHA

Learning Outcomes

  • explain the role of CAPTCHAs in securing web applications
  • secure a web application using CAPTCHAs
  • utilize a 3rd party service in a web application backend

Resources

Lab

Video walk through of this lab.

You can start with your own completed REST assignment, or if there are problems with that, you can use the https://gitlab.com/langarabrian/rest2-final project. If you use that one, be sure to fork, remove the fork relationship and make private.

Set the MONGODBURI environment variable as per previous exercises.

Install the dependencies and run in dev mode:

1
2
3
cd rest2-final
npm install 
PORT=8080 npm run dev

Verify that the application is running in the web preview.

In a new terminal tab, try this (remove the spaces around “localhost” first):

1
2
3
curl -d '{"firstName":"Bob", "lastName":"Smith", "email":"[email protected]", "country":"USA", "province":"WA", "postalCode":"12345"}' \
    -H "Content-Type: application/json" \
    -X POST http:// localhost :8080/signups

Refresh your app in the web preview tab and confirm that the new record appears in the table of signups. This means our app is vulnerable to bots that could create 1,000,000s of signups without any human interactions. In the next section we will add a CAPTCHA to discourage bots.

Configure reCAPTCHA Service

Go to the Google reCAPTCHA Admin Console.

Type in a label, like “Signup App”.

For “reCAPTCHA Type”, choose “reCAPTCHA v3”.

For “Domains”, enter the domain from your web preview (which would be something like 8080-dot-3969277-dot-devshell.appspot.com)

Accept the reCAPTCHA Terms of Service.

Click on the “SUBMIT” button.

Record the “Site” and “Secret” keys.

Configure reCAPTCHA in the Frontend

In public/index.html, add the following just before “form-validation.js” (replacing YOUR_SITE_KEY with your actual site key):

<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>

In public/form-validation.js, wrap all of the code inside the “load” event as follows. This ensures the reCAPTCHA API is loaded and initialized before we attempt to use any of its functions:

window.addEventListener('load', function() {
  grecaptcha.ready(function() {
      // all the other code
  });
}, false);

Replace this code block:

signups.create({
  firstName: $('#firstName').val(),
  lastName: $('#lastName').val(),
  email: $('#email').val(),
  country: $('#country').val(),
  province: $('#province').val(),
  postalCode: $('#postalCode').val()
});
form.classList.remove('was-validated');
form.reset();

with this (replacing YOUR_SITE_KEY with your acutal site key). This guards the action we want to make sure is completed by a human and not a bot:

grecaptcha.execute('YOUR_SITE_KEY', {action: 'signup'}).then(function(token) {
  signups.create({
    firstName: $('#firstName').val(),
    lastName: $('#lastName').val(),
    email: $('#email').val(),
    country: $('#country').val(),
    province: $('#province').val(),
    postalCode: $('#postalCode').val(),
    token: token
  });
  form.classList.remove('was-validated');
  form.reset();
});

Back in the terminal tab where the app is running, type Ctrl-C to stop the Node app temporarily.

Configure reCAPTCHA in the Application Backend

We are going to create a hook function to be called before a new signup is created to validate the reCAPTCHA:

npm i -g @feathersjs/cli    # only if you need to
feathers generate hook

Answer the questions as follows:

1
2
3
4
What is the name of the hook? process-signup
What kind of hook should it be? before
What service should this hook be for? signups
What methods should the hook be for? create

In the newly generated file, src/hooks/process-signup.ts, add the following imports to bring in modules that we will need to interact with the reCAPTCHA verification service:

// used to build the query string passed to the reCAPTCHA service
import querystring from 'querystring';

// used to connect with the reCAPTCHA service
import axios from 'axios';

The generated hook function does nothing by default. It takes a single parameter, context, which contains information about the incoming service request and returns it unmodified, without performing any additional actions:

return context;

Replace the body of the hook function with the following code block (using your actual secret key):

    // extract the incoming request data
    const { data } = context;
    
    // FYI
    console.log( data );
    
    // verify the incoming token against the reCAPTCHA service
    const response = await axios.post(
      'https://www.google.com/recaptcha/api/siteverify',
      querystring.stringify({
        secret: process.env.RECAPTCHA_SECRET,
        response: data.token
      })
    );
    
    // FYI
    console.log(response.data);
    
    // if the response fails or the score is too low, throw an error
    if ( !response.data.success || response.data.score < 0.95 ) {
      throw new Error('reCAPTCHA fail');      
    }
    
    // if everything is OK, carry on
    return context;

Set the RECAPTCHA_SECRET environment variable as follows (using your actual secret):

1
export RECAPTCHA_SECRET=your-actual-secret-here

Re-start the backend. Try subitting the form. Note your score in the backend terminal window. Play around with the response.data.score threshold to get something acceptable.

Right now there is nothing in the frontend UI to indicate that the user has failed the reCAPTCHA. If you look in the web console in the browser, you can see the thrown exception. Let’s add a block in the HTML that is normally hidden. We will reveal it when we detect the exception. In the public/index.html file, add the following just above the “Signup” button:

<div class="alert alert-danger d-none" role="alert" id="captchaMessage">
    Failed reCAPTCHA
</div>

Handle reCAPTCHA Fails Gracefully in the Frontend

In public/form-validation.js, wrap the the call to signups service create methond in a try/catch block as follows:

signups.create({
  firstName: $('#firstName').val(),
  lastName: $('#lastName').val(),
  email: $('#email').val(),
  country: $('#country').val(),
  province: $('#province').val(),
  postalCode: $('#postalCode').val(),
  token: token
})
.then(function() {
  $('#captchaMessage').addClass('d-none');
  form.classList.remove('was-validated');
  form.reset();
})
.catch(function() {
  $('#captchaMessage').removeClass('d-none');
});

Assignment

  1. Complete the Feathers JS Tutorial in The Feathers guide with the following modifications:
    • complete the TypeScript version
    • instead of the NeDB datastore, use the Mongoose driver instead
  2. Incorporate reCAPTCHA v3 into the tutorial so that a new account is only registered if the score is above 0.7.