Stop Spam with Javascript and Ruby on Rails

Posted by Daniel on 03/23/2009

Spammers suck

We've all seen the blog comments. You know the ones I mean. The ones that reference pictures or videos, the kind that aren't exactly 'socially acceptable.' Spam has become a huge issue in today's world, especially spam delivered via email. We all hate it, but it comes anyway. Entire companies have sprung up just to stop it, and still it comes, relentlessly. That relentless annoyance has started spilling over to the web, in the form of blog comments, and forum posts. If you're fortunate enough to have a semi-popular website, you know that spam is a real issue.

I've seen a number of solutions out there to prevent spam like posts from showing up on a Ruby on Rails site. Ryan Bates did a great job showing how to stop spam using Akismet. This appears to be a very effective method of stopping spam. I've also seen and used captchas with RoR. I have problems with both these solutions. Integrating Akismet can be a pain, and seems like a lot of code and work to stop spammers. Using a captcha hurts the usability of your application.


Understanding the problem

We get spam because it's easy to create. It doesn't cost a dime to send, at least not for the spammer. They control endless hordes of spam bot infected Windows systems. There is no human interaction required to send a spam message or post a spammy comment. This is the spammer's greatest strength, but it is also their greatest weakness. This is why captchas are so popular and effective. Let's take the principle of the captcha and make it seamless.

My solution, no warranties expressed or implied

I recently created a site with a comment section. It isn't a very popular site yet, but it was still getting spam comments. I thought about adding a captcha, but they're a pain. So, I figured why not add a virtual attribute to the model, check to see if it's present in a form submission before saving? If it isn't there, just don't save the record, don't give any errors, and give a valid response so the spam bot is none the wiser.

Here is my model:

class Comment < ActiveRecord::Base
  attr_accessor :key
  
  validates_presence_of :name
end

My controller action:

def create
  @comment = Comment.new(params[:comment])
  if !@comment.key == "YourGeneratedorStaticKeyHere"
    if @comment.save
      flash[:notice] = "Successfully created comment."
      redirect_to some_path
    else
      redirect_to some_path
    end
  end
end

And my form view:

<div id="form">
    <% form_for @comment do |f| %>
      <%= f.error_messages %>
      <p>
        <strong>Name: </strong><small>(required)</small><br />
        <%= f.text_field :name %>
      </p>
      <p>
        <strong>Email: </strong><br />
        <%= f.text_field :email %>
      </p>
      <p>
        <strong>Website Url:</strong><br />
        <%= f.text_field :url %>
      </p>
      <p>
        <%= f.text_area :comment %><br />

      </p>
      <p class="attributes">
      </p>
      <p>
        <%= f.submit "Submit" %>
      </p>
    <% end %>
    
</div>
<script type="text/javascript">
    $(document).ready(function() {
        $('#new_comment').submit(function() {
            $('.attributes').append('<input id="comment_key" type="hidden" value="YourGeneratedorStaticKeyHere" name="comment[key]">');
        });
    })
</script>

As you can see, I add a hidden field to the form via jQuery. You can create your key in whatever manner you wish. An Ajax call back to the server for a key? Something static? I'll leave that up to you.

What it is, and what it isn't

As you can see, this form relies on the fact that someone is loading the page in a JS compatible browser. This is to combat the spam bots that are using curl like commands to fake a form submission, without ever looking at the page. It will not keep someone from navigating to the site and posting "Click here to see what my sister can do!" type of deal. It probably won't work on a larger site, with a statically set key. I think it will work for the small time blogger or website though. Time will tell. I don't think that a spammer will take the time out of his day to look at this one measly site and see why his comments aren't posting. There are 1000's of easier targets that his spam bot's will tackle on it's own.

Thoughts and comments welcome!

Comments.

1. Ivan Storck wrote:

This is a great way to stop spam. However, I couldn't get the jQuery to work just right, probably because I don't know jQuery well enough. I had to modify it to:

        $(document).ready(function() {
            $('#comment_form').append('');
            })

(for some reason the input tag is not showing up in this comment)


2. Daniel Westendorf wrote:

@Ivan - Make sure you're pointing it at the ID of the new comment form element, not just the DIV wrapping the form. In my example above using the form_for @comment renders an HTML form element with an ID of "new_comment".


Name: (required)

Email: (not shown or published)

Website Url:


(Use Markdown Syntax, <pre> your code </pre>)