0.0
The project is in a healthy, maintained state
verify_human class macro, human_verification_tag view helper, and a Turbo-safe Stimulus controller. Interaction-only Managed widgets by default, optional invisible-mode widgets, fail-open verification with tight timeouts, per-form action enforcement.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 2.0
>= 7.1
 Project Readme

verify_human

Human verification for Rails forms. Cloudflare Turnstile under the hood; provider-agnostic API on the surface.

This is an opinionated gem: it does human verification the way I like it in my own Rails apps. It's shared in case that way suits you too, not as a general-purpose library that tries to fit every setup.

Installation

gem "verify_human"

Usage

# ApplicationController (or a specific controller)
include HumanVerification

# Any controller with a form bots abuse. Guards :create by default;
# declare AFTER rate_limit so the cheap local check runs first.
rate_limit to: 5, within: 15.minutes, only: :create
verify_human "sign_in"
<%# In the form. Invisible for ~98% of visitors (interaction-only Managed
    widget); shows a one-click checkbox only when Cloudflare requires it. %>
<%= human_verification_tag "sign_in" %>

<%# Submit the surrounding form as soon as a token is issued: %>
<%= human_verification_tag "apply", auto_submit: true %>

<%# Never show UI at all (separate invisible-mode widget; visitors who can't
    pass silently post tokenless -- pair with a retry/interstitial flow): %>
<%= human_verification_tag "apply", invisible: true %>

Override the failure response per controller (default: redirect_back with an alert):

def human_verification_failed
  flash.now[:alert] = "We couldn't verify you're human. Please try again."
  render :new, status: :unprocessable_entity  # frame-safe, preserves input
end

Configuration

Reads Rails credentials; verification no-ops unless BOTH keys are present (dev/test are zero-config):

turnstile:
  site_key: ...
  secret_key: ...
  invisible_site_key: ...    # only if you use invisible: true
  invisible_secret_key: ...

Create Managed-mode (and optionally invisible-mode) widgets in the Cloudflare dashboard or via API. Development can use Cloudflare's always-pass test keys. Invisible mode requires referencing Cloudflare's Turnstile Privacy Addendum in your privacy policy.

Behavior worth knowing

  • Fails open on Cloudflare outages (2s/3s timeouts, under a 20s rack-timeout budget); a blank token is rejected without an HTTP call.
  • The siteverify action is enforced per form name: a token minted on one widget can't be spent on another endpoint.
  • The Stimulus controller is Turbo-safe: container cleared around Turbo caching (TurnstileError 300030), script-load onerror recovery, no render-after-disconnect, and a fresh widget after a Turbo morph (e.g. a form re-rendered with 422 on a page using turbo_refreshes_with method: :morph).
  • Users who block challenges.cloudflare.com cannot pass (fail closed by nature of CAPTCHAs) -- keep failure paths friendly.

Tests

require "verify_human/test_helpers"   # needs WebMock

include HumanVerification::TestHelpers
with_human_verification_configured { ... }
stub_human_verification(success: true, action: "sign_in")
stub_human_verification_outage

Contributing

Bug reports are welcome as GitHub issues. I don't accept pull requests: the gem stays as opinionated as it is, so please fork it if you want it to work differently.

Releasing

Bump VerifyHuman::VERSION, tag vX.Y.Z, push the tag. GitHub Actions publishes to rubygems.org as a trusted publisher, so no API key is stored anywhere.

License

MIT. See LICENSE.