Oh my god. It's full of code!

Apex Captcha with Javascript Remoting and jQuery

So at one time or another, we’ll likely all have to create a public facing form to collect data. We will also find about 2 seconds afterwards that it is getting spammed to hell. To stop the flood of crap, we have reCaptcha. An awesome little utility that will prevent bots from submitting forms. You already know what captcha is though, that’s probably how you found this post, by googling for apex and captcha. First off, there is already an awesome post on how to do with by Ron Hess (Here), but his approach is a bit complicated, and visualforce heavy. Of course being kind of anti visualforce, and the complexities of properties and all that, I made my own little approach. So here we go.

This is assuming you already signed up with reCaptcha. You can go here and sign up for recaptcha (yes you can just enter force.com as the domain)
After that, course add an entry for google to your remote sites in the admin setup under security. Disable protocol security.
Then create your visualforce page, and apex class. I called my class utilities, since this is kind of a re-usable function and I wanted to keep it generic.

Now put this crap in your controller. Also, your controller needs to be global (to use javascript/apex remoting)

@RemoteAction
    global static boolean validCaptcha(string challenge, string response)
    {
      boolean correctResponse = false;
      string secret = 'your recaptcha secret key here. Maybe make this into a custom setting?';
      string publicKey = 'your recaptcha public key here. Maybe make this into a custom setting?';
      string baseUrl = 'http://www.google.com/recaptcha/api/verify'; 

      string body ='privatekey='+ secret +  '&remoteip=' + remoteHost() + '&challenge=' + challenge + '&response=' + response + '&error=incorrect-captcha-sol';
      
      HttpRequest req = new HttpRequest();   
      req.setEndpoint( baseUrl );
      req.setMethod('POST');
      req.setBody ( body);
      try 
      {
        Http http = new Http();
        HttpResponse captchaResponse = http.send(req);
        System.debug('response: '+ captchaResponse);
        System.debug('body: '+ captchaResponse.getBody());
        if ( captchaResponse != null ) 
        {  
            correctResponse = ( captchaResponse.getBody().contains('true') );
        }          
       
      } 
      catch( System.Exception e) 
      {
         System.debug('ERROR: '+ e);
      }                             
      return correctResponse;
    }

    global static string remoteHost() 
    { 
        string ret = '127.0.0.1';
        // also could use x-original-remote-host 
        try
        {
            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');
            }
        }
        catch(exception e)
        {
        
        }
        return ret;
    }

Ok, great, now your controller is ready. You just need to pass the right info and it will tell you if it’s right or wrong. Lets get a visualforce page set up to do that.

<apex:page controller="utilities" standardStylesheets="false" sidebar="false"  >

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" />


<script>
$(function() {

    $( "#validateButton" ).click(function(){
        
        validCaptcha(function(valid){
            if(valid)
            {
                $('#validationResultDiv').html('Valid Captcha!');
                
                //Do whatever here now that we know the captcha is good.
            }
            else
            {
                $('#validationResultDiv').html('Invalid Captcha Entered');
            }
           
        });           
    });
});

function validCaptcha(callback)
{
    var challenge = document.getElementById('recaptcha_challenge_field').value;
    var response = document.getElementById('recaptcha_response_field').value;

    utilities.validCaptcha(challenge,response, function(result, event)
    {
        if(event.status)
        {
           callback(result);
        }
    }, {escape:true});
}

</script>

<div id="captchaEnter" title="Form Submission Validation">
    <center>
    <script type="text/javascript" src="https://www.google.com/recaptcha/api/challenge?k=YOUR PUBLIC KEY GOES HERE DONT FORGET IT"></script>
    <noscript>
       https://www.google.com/recaptcha/api/noscript?k=YOUR_PUBLIC_KEY
     </noscript>  
     <div id="validationResultDiv"></div>   
     <button id="validateButton" class="inline">Submit</button>
       
     </center>
</div>


</apex:page>

Boom! Just that easy. Hook up an event handler to the submit button that runs the validCaptcha function. It will get the proper values, and send them to the apex class, which sends them to reCaptcha to verify. Once an answer comes back, it is passed into the callback function, which the can run whatever action you require. Don’t forget to replace the place holder public key in the script line above. Have fun!

2 responses

  1. There is no way the call to Remotehost can work in the remote action, since apexPages.currentPage() is null. So the call is not needed as it will never work. There doesn’t appear to be a way to get the ipaddress in the remoteaction. Which is bad form by salesforce.

    September 18, 2015 at 6:44 pm

  2. Anonymous

    As of Winter 15 you can use mymap = Auth.SessionManagement.getCurrentSession() and which returns a map. Then do ip = mymap.get(‘SourceIp’).

    January 11, 2017 at 8:19 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s