Adding CAPTCHA to Force.com Sites

A CAPTCHA is a challenge-response test used in applications to determine whether a human or a computer is interacting with the application. You've probably seen them — colorful images with distorted text at the bottom of Web registration forms. CAPTCHAs are used by many websites to prevent abuse from "bots," or automated programs usually written to generate spam. No computer program can read distorted text as well as humans can, so bots have a hard time navigating sites protected by CAPTCHAs.

This tutorial describes how to add a CAPTCHA challenge to your Force.com Site. It uses the popular reCAPTCHA technology to implement the challenge. This tutorial provides the source code for adding such a feature to your own applications.

Why reCAPTCHA?

To archive human knowledge and to make information more accessible to the world, multiple projects are currently digitizing physical books that were written before the computer age. The book pages are being photographically scanned, and then transformed into text using Optical Character Recognition (OCR). The transformation into text is useful because scanning a book produces images, which are difficult to store on small devices, expensive to download, and difficult to search. The problem is that OCR isn't perfect.

reCAPTCHA improves the process of digitizing books by sending words that can't be read by computers to the Web in the form of CAPTCHAs for humans to decipher. More specifically, each word that cannot be read correctly by OCR is placed on an image and used as a CAPTCHA.

reCAPTCHA in Action

Imagine creating a web site where you needed to ensure a human was responding. For example, you want to ensure that a human was filling in a form before creating a new account for them. You could do this by adding a CAPTCHA to the page, to challenge the user as shown in the following figure:

Typical reCAPTCHA widget

This article includes sample code, a Visualforce page with an associated Apex controller, that illustrates this in action — it tests your humanity. It's not a complete application, but it should give you the gist. Using the techniques from this tutorial you'll be able to code such CAPTCHAs into your own sites.

Understanding CAPTCHA

The following diagram can be found on the reCAPTCHA site, which I've included here for your reference. reCAPTCHA diagram

As described in the reCAPTCHA API Documentation:

  • The user loads the web page with the reCAPTCHA challenge JavaScript embedded.
  • The user's browser requests a challenge from reCAPTCHA. reCAPTCHA gives the user a challenge, and a token that identifies the challenge.
  • The user fills out the web page form, and submits the result to your application server, along with the challenge token.
  • Via an API call from your application, reCAPTCHA checks the user's answer, and gives you back a response.
  • If true, generally you'll allow the user access to some service or information. E.g. allow them to comment on a forum, register for a wiki, or get access to an email address. If false, you can allow the user to try again.

Enough of the theory, let's get to the practice!

Getting Started

Your first step is to sign up for a CAPTCHA key — you will need that in the code below. When I signed up for the key, I used the global key option.

Now you need to modify a few security settings in your environment. In particular, you need to enable callouts from Apex Code to a remote site. Our code does an HTTP.send() to this site. To do this:

  • Under Setup | Security Controls find Remote Site Settings , and add the following URL: https://www.google.com.

Remote site settings

The figure above shows how we set it up for our developer environment.

An Apex Code Helper

I've created a simple helper class to use when implementing the reCAPTCHA. It's listed below. This code is free to use, under the new BSD open source license. Add your public and private key to this class by replacing the placeholders (see the first few lines) with the keys you got when you signed up for your reCAPTCHA account.

public class reCAPTCHA {

    /* Configuration */

    // The API endpoint for the reCAPTCHA service
    private static String baseUrl = 'https://www.google.com/recaptcha/api/verify'; 

    // The keys you get by signing up for reCAPTCHA for your domain
    private static String privateKey = 'your_private_key';
    public String publicKey { 
        get { return 'your_public_key'; }
    } 

    /* Implementation */
    
    // Simple form fields for the example form
    public String myName { get; set; }
    public String myEmail { get; set; }
    
    // Create properties for the non-VF component input fields generated
    // by the reCAPTCHA JavaScript.
    public String challenge { 
        get {
            return ApexPages.currentPage().getParameters().get('recaptcha_challenge_field');
        }
    }
    public String response  { 
        get {
            return ApexPages.currentPage().getParameters().get('recaptcha_response_field');
        }
    }
    
    // Whether the submission has passed reCAPTCHA validation or not
    public Boolean verified { get; private set; }
    
    public reCAPTCHA() {
        this.verified = false;
    }
    
    public PageReference verify() {
        System.debug('reCAPTCHA verification attempt');
        // On first page load, form is empty, so no request to make yet
        if ( challenge == null || response == null ) { 
            System.debug('reCAPTCHA verification attempt with empty form');
            return null; 
        }
                    
        HttpResponse r = makeRequest(baseUrl,
            'privatekey=' + privateKey + 
            '&remoteip='  + remoteHost + 
            '&challenge=' + challenge +
            '&response='  + response
        );
        
        if ( r!= null ) {
            this.verified = (r.getBody().startsWithIgnoreCase('true'));
        }
        
        if(this.verified) {
            // If they pass verification, you might do something interesting here
            // Or simply return a PageReference to the "next" page
            return null;
        }
        else {
            // stay on page to re-try reCAPTCHA
            return null; 
        }
    }

    public PageReference reset() {
        return null; 
    }   

    /* Private helper methods */
    
    private static HttpResponse makeRequest(string url, string body)  {
        HttpResponse response = null;
        HttpRequest req = new HttpRequest();   
        req.setEndpoint(url);
        req.setMethod('POST');
        req.setBody (body);
        try {
            Http http = new Http();
            response = http.send(req);
            System.debug('reCAPTCHA response: ' + response);
            System.debug('reCAPTCHA body: ' + response.getBody());
        } catch(System.Exception e) {
            System.debug('ERROR: ' + e);
        }
        return response;
    }   
        
    private String remoteHost { 
        get { 
            String ret = '127.0.0.1';
            // also could use x-original-remote-host 
            Map<String, String> hdrs = ApexPages.currentPage().getHeaders();
            if (hdrs.get('x-original-remote-addr')!= null)
                ret =  hdrs.get('x-original-remote-addr');
            else if (hdrs.get('X-Salesforce-SIP')!= null)
                ret =  hdrs.get('X-Salesforce-SIP');
            return ret;
        }
    }
}

The key to this code is the verify() method, which makes the HTTP POST to the reCAPTCHA site. Note that it stores the result of the challenge in the verified property.

Example Visualforce Page

Here's the Visualforce page. It will only test your humanity — it doesn't integrate with any other forms or input functionality. You'll have to integrate this source code into your own application as you see best. Normally you would pass your input elements in this same submit, and then inside the Apex code (in the method verify) you would perform the captcha and then take actions depending on the value of the boolean verified.

<apex:page controller="reCAPTCHA">
  <apex:pageBlock title="Bot or Not">
    <apex:form >
        <apex:pageBlockSection columns="1">
            <apex:pageBlockSectionItem >
                <apex:outputLabel for="inputName" value="Name"/>
                <apex:inputText value="{!myName}" id="inputName"/>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
                <apex:outputLabel for="inputEmail" value="Email"/>
                <apex:inputText value="{!myEmail}" id="inputEmail"/>
            </apex:pageBlockSectionItem>

            <!-- Show the reCAPTCHA form if they are not yet verified -->
            <apex:pageBlockSectionItem rendered="{! NOT(verified)}">
                <!-- reCAPTCHA verification 
                     Source: https://developers.google.com/recaptcha/docs/display -->
                <script type="text/javascript"
                    src="https://www.google.com/recaptcha/api/challenge?k={!publicKey}">
                </script>
                <noscript>
                    <iframe src="https://www.google.com/recaptcha/api/noscript?k={!publicKey}"
                    height="300" width="500" frameborder="0"></iframe><br/>
                    <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
                    <input type="hidden" name="recaptcha_response_field"
                        value="manual_challenge"/>
                </noscript>
                <!-- end reCAPTCHA verification -->
            </apex:pageBlockSectionItem>
            
            <apex:pageBlockSectionItem >
                <apex:commandButton action="{!verify}" 
                        value="Check If I'm Human" rendered="{! NOT(verified)}"/>
            </apex:pageBlockSectionItem>
            
            <!-- Otherwise, they are verified, show a confirmation message -->
            <apex:pageBlockSectionItem rendered="{!verified}">
                <p>reCAPTCHA verification suggests that you're not a 'bot.</p>
            </apex:pageBlockSectionItem>            
        </apex:pageBlockSection>
        
    </apex:form>
  </apex:pageBlock>
</apex:page>

Summary

In summary, this is an easy and fun project to add a human challenge-response system to your Force.com Sites input forms. It should dramatically reduce the amount of form spam you get.

On the do-it-yourself difficulty scale (1-easy, 5-complex), adding this feature to your application is about a 3 out of 5, only because you need to add your form fields in the correct place and then avoid creating records in your database if the verified expression is false in your controller.

Good luck!

References