hCaptcha: How to integrate it into your Ruby on Rails application and why.

Andrés

9 September 2023

hCaptcha: How to integrate it into your Ruby on Rails application and why. hCaptcha defines itself as the largest independent catpcha service. They claim to be running on 15% of the internet and that it is possible to compete with the tech giants by focusing on privacy. Let's see how easy it is to install this captcha in a Ruby on Rails application.

The main function of a captcha is to prevent the passage of bots to our web pages, but this task has become increasingly difficult over time. Both good and bad bots have become more sophisticated. And that is why so far the best way to distinguish a real user from a machine, without compromising the real identity of the users, is through the use of challenges.

If we want to preserve our privacy online, we need ways to verify that a person is interacting with online services without trying to link that to anyone’s real identity.

Why not to use Google reCaptcha?

Google is primarily an advertising business. Their product is their users’ data (their “used” as Richard Stallman would say). And every time they encounter a bot it’s not business for them, so we could ask: What is their real interest in stopping bots? Probably, it’s to get more data about users. And that’s how they made their reCatpcha v2 solution obsolete and launched reCaptcha v3, which they recommend you embed on every page of your site and which requires no user interaction.

How do you make it so that there is no user interaction to define if they are bots or not? Data and more data linked to real identities that they most likely use for more than just defining if it is a bot or not. Maybe you have noticed that using a clean web browser (without cookies or history) it has taken you much longer to solve a Google captcha than when you have a Google account open or have browsed several sites already. You can read more about this topic here.

The approach of storing everything also hurts the user experience, violates privacy and ends up punishing real people who have not opted for the Google ecosystem, for example, by using Firefox.

We start with our application.

This is a basic application that does not contemplate all possible use cases. It is only a happy path

We will create a login where we will integrate hCatpcha. We will see how to integrate it in the login view, as well as the server-side verification of the token generated by the captcha.

rails new hcaptcha --css tailwind

Create our User model:

rails g model user email:string password_digest:string

Run migrations with rails db:migrate and then create our controller for make logins work:

rails g controller authentication new create show

We will add has_secure_password to our User model and quickly create a new user by logging into the rails console with rails c and then User.create(email: "[email protected]", password: "Test12345").

And finally we will make our routes.rb file look like this:

Rails.application.routes.draw do
  root 'authentication#new'
  resources :authentication, only: %i[create show]
end

hCaptcha’s account

You must create an account here and get a site_key and a secret_key.

We copy the sitekey and add it to our crendetials.yml with the name hcatpcha_site_key.

We copy the secret key and add it to our crendetials.yml with the name hcatpcha_secret_key.

Views

We will create a simple form where we will ask for the email and password. Inside the same form we will add the necessary lines for the captcha to be displayed and we will also add a simple way to display flash messages. Then our app/views/authentication/new.html.erb view would look like this:

<div class="flex min-h-full flex-col justify-center px-6 py-12">
  
  <% flash.each do |type, msg| %>
    <div>
      <%= msg %>
    </div>
  <% end %>

  <h2 class="mt-10 text-center text-2xl font-bold text-gray-900">Entrar</h2>

  <div class="mt-10">
    <%= form_with url: authentication_index_path do |f| %>
      <%= f.text_field :email, class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600" %><br>
      <%= f.password_field :password ,class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600" %><br>

      <!-- hCaptcha -->
      <div class="h-captcha" data-sitekey="<%= Rails.application.credentials.hcaptcha_site_key %>"></div>
      <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
      <!-- end hCaptcha -->

      <%= f.submit "Entrar", class: "flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %>
    <% end %>
  </div>
</div>

Note that the site_key is obtained dynamically from our credentials.yml file.

Check the data obtained in the form.

Before working on our controller we must know that we will need a method to verify the response of our captcha. For this we will create a concern that will be called HcatpchaConcern and will have the following code:

require 'net/http'

module HcaptchaConcern
  extend ActiveSupport::Concern

  def verify(params)
    uri = URI('https://hcaptcha.com/siteverify')
    res = Net::HTTP.post_form(uri, 'secret' => Rails.application.credentials.hcaptcha_secret_key,
                                   'response' => params['g-recaptcha-response'])

    response_data = JSON.parse(res.body)

    return if ActiveModel::Type::Boolean.new.cast(response_data['success'])

    flash[:notice] = 'hCaptcha could not be verify.'
    redirect_to request.referrer || root_path
  end
end

Now if in our controller we will use this concern and verify everything else:

class AuthenticationController < ApplicationController
  include HcaptchaConcern

  before_action -> { verify(params) }, only: :create

  def new; end

  def create
    session_params = params.permit(:email, :password)
    @user = User.find_by(email: session_params[:email])

    flash[:notice] = if @user && @user.authenticate(session_params[:password])
                       'Login is valid!'
                     else
                       'Login is invalid!'
                     end

    redirect_to root_path
  end
end

Results


Some things to take into consideration:

  • If you are going to develop on localhost I recommend you see this..
  • You can see all the hcaptcha documentation here

In conclusion, integrating hCaptcha into your Ruby on Rails application offers an effective and ethical solution to protect your web pages from bots and preserve the online privacy of your users. Unlike other options, such as Google’s reCaptcha, hCaptcha focuses on security without compromising users’ identities or collecting intrusive data. By choosing hCaptcha, you are not only protecting your website from automated threats, but you are also contributing to a safer and more privacy-friendly online environment for your visitors. This integration can significantly improve the user experience and ensure safer access to your online services.

Carbon impact of this web page
🌟 Let's connect on X/Twitter! 🌟