Friday, June 21, 2013

Ruby on Rails, Recaptcha, AJAX

(excuse the formatting of this post, I can't be bothered right now to deal with the Blogger rich-text editor:P)

So I launched www.capoapp.ca and www.bitesite.ca over a year ago and it was only a matter of time before bot spammers started hitting my website. I quickly started seeing all these nonsense e-mail notifications from people filling out my feedback and contact forms among other public forms.

Without even knowing they were bots, my first attempt to stop these spammers was using a Captcha solution. So I decided to use the www.recaptcha.com and more specifically, the recpatcha gem found here.

I used this on www.capoapp.ca and luckily, within days, the spam stopped. It looks like I had found my solution.

So I implemented recaptcha on www.capoapp.ca's Feedback page and Submit an Open Mic page and all was good.

My next step was to implement it for my company's corporate website, www.bitesite.ca.

I added the recatpcha_tags as instructed, and also added the verify_recaptcha code to the server-side controller (just like I did with www.capoapp.ca), but to no avail.

Capo was working perfectly fine but BiteSite wasn't. What gives?

The problem was actually quite obvious (I must have just not slept well - well actually I was coding BiteSite's solution when I was 12-hours jet-lagged). And while the problem was obvious, the solution wasn't really.

What was the problem? Well it had to do with the submission of the form. In Capo, I was submitting the form by posting the page to the server - and because the recatpcha_tags were part of the form, they automatically went with the form to the controller - and all the necessary components were there for verify_recpatcha to process.

BiteSite on the other hand, I was submitting the form via a JQuery AJAX call. So the fields in my form aren't automatically submitted. In the past, here's what my BiteSite contact form submit AJAX code looked like:

var first_name = $("#first_name").val();
var last_name = $("#last_name").val();
var email_address = $("#email_address").val();
var message = $("#message").val();
$.ajax({
  url:"/contact",
  type: "POST",
  data: { first_name : first_name,
          last_name : last_name,
          email_address : email_address,
          message : message },
  dataType: "json",
  success:function(data, textStatus, jqXHR){
    ...
  },
  error:function(jqXHR, textStatus, errorThrown){
  ...
  }
});
So as you can see, I'm manually passing first_name, last_name etc. to my controller. The issue is that for verify_recaptcha, I need to pass what the user entered for the recaptcha_tags. On top of that, I need to pass any additional information that verify_recaptcha needs to do its work.
So that was the problem?

What's the solution? I need to figure out exactly what to pass to my controller so that verify_recaptcha will work. So luckily, the recatpcha gem is open-source, and I just browsed the code to find that verify_recatpcha relies on 2 main parameters:
params[:recaptcha_challenge_field]
params[:recaptcha_response_field]
So that was it. My Ajax call was missing these two parameters, so all I had to do was change my code to this:
var first_name = $("#first_name").val();
var last_name = $("#last_name").val();
var email_address = $("#email_address").val();
var message = $("#message").val();
var recaptcha_response_field = $("#recaptcha_response_field").val();
var recaptcha_challenge_field = $("#recaptcha_challenge_field").val();
$.ajax({
url:"/contact",
type: "POST",
data: { first_name : first_name, 
last_name : last_name, 
email_address : email_address, 
message : message,
recaptcha_challenge_field : recaptcha_challenge_field,
recaptcha_response_field : recaptcha_response_field },
dataType: "json",
success:function(data, textStatus, jqXHR){
...
},
error:function(jqXHR, textStatus, errorThrown){
...
}
});
So that was it. Now it all works fine. Take that bots! (shouldn't egg on the spammers, they're way better programmers than I am and now they're gonna attack me).
Hope that helps out some peeps.