What is reCAPTCHA and why is it important?
reCAPTCHA is a tool designed by Google to help protect websites from spam messages or bots. You've probably seen it before - v2 is the 'I'm not a robot' checkbox or spotting the bridge in a grid of images.
Scripts, bots and most kinds of automated software can't solve these challenges in a convincingly 'human' way. For example, a bot would take a slow and direct route to a v2 checkbox, while a human may move their mouse around the page, click on other elements, and take a few seconds to check the box.
As developers, we want to reduce the amount of 'noise' messages we receive via forms on our websites. reCAPTCHA is a great tool for doing this.
You should go to the reCAPTCHA admin console and create a new site. You'll need to enter a label (for your own reference) and the domain of your website. You can also choose which reCAPTCHA version you want to use - we'll be using v2.
NOTE: for development purposes, you can add
Localhost
to the list of domains. This will allow you to test reCAPTCHA on your local machine.
Once you've created your site, you'll be given a site key and a secret key. Keep a note of these - we'll need them later.
In this short guide, I will be using NextJS and React with TypeScript.
We'll be using the react-google-recaptcha package to add reCAPTCHA to our React app.
If you're using a different Frontend, you'll need to find a package that works for you. But, the process remains similar:
.env
file or other secret management toolIn the frontend, we will be using the reCAPTCHA element (tickbox in this case) which creates a token that we can send to the backend for verification. Although you can verify the token on the frontend, it's best practice to do this on the backend.
.env
file
REACT_APP_RECAPTCHA_SITE_KEY
.NEXT_PUBLIC_
i.e. NEXT_PUBLIC_RECAPTCHA_SITE_KEY
. Why? Read more here.ReCAPTCHA
from the react-google-recaptcha
import ReCAPTCHA from 'react-google-recaptcha'
<ReCAPTCHA
ref={}
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
onChange={}
/>
ref
prop is used to reset the reCAPTCHA element. We'll use this later.sitekey
prop is the site key we saved earlier.onChange
prop is a function that will be called when the user completes the reCAPTCHA challenge. We'll use this to save the token to state.useState
to save the token to state: const [recaptchaToken, setRecaptchaToken] = useState<string | null>(null);
useRef
to create a reference to the reCAPTCHA element: const recaptchaRef = useRef<ReCAPTCHA>(null);
handleCaptchaChange
:const handleCaptchaChange = (value: string | null) => {
if (value === null) {
return;
}
setRecaptchaToken(value);
};
recaptchaRef
and handleCaptchaChange
: <ReCAPTCHA
ref={recaptchaRef}
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
onChange={handleCaptchaChange}
/>
Hopefully, at this point, the reCAPTCHA should render on your form. If not, try restarting your console.
One thing I had to do to get the initial reCAPTCH to work was to wrap the reCAPTCHA in a conditional statement. In TypeScript React apps, process.env
is not treated as defined, even if it is explicitly defined in your .env
file.
{process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY && (
<ReCAPTCHA
ref={recaptchaRef}
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
onChange={handleCaptchaChange}
/>
)}
We need to reset the reCAPTCHA element when the form is submitted. This is so that the user can't submit the form multiple times with the same token.
This is where useRef
comes in. In your form's onSubmit
function, you can reset the reCAPTCHA element by calling recaptchaRef.current?.reset()
.
e.g.
// note: I'm using react-hook-form here
const onSubmit: SubmitHandler<FormValues> = async (data) => {
// ... other form submission logic
// check if the user has completed the reCAPTCHA challenge
if (recaptchaToken === || !recaptchaToken) {
// ... here you can also add a message to the user to complete the reCAPTCHA challenge, or some kind of loading or error handling
// if not, return early
return;
}
// ... submit request to backend - using a try/catch block
try {
// ... submit request to backend
// NOTE: remember to send the recaptchaToken to the backend for verification
} catch (error) {
// ... handle error
} finally {
// ... reset the reCAPTCHA element
recaptchaRef.current?.reset();
}
}
In summary, use a try/catch/finally
block (good practice) to make your request and in the finally
block, reset the reCAPTCHA reference.
Also, note that you should be sending the reCAPTCHA token to the backend for verification. We'll cover this in the next section.
You should now have a working reCAPTCHA element on your form. When the user completes the challenge, the token should be saved to state. When the form is submitted, the reCAPTCHA element should be reset - regardless of whether the form is submitted successfully or not.
In my case, I did this in a Rails app. But, the process should be similar for other backend frameworks.
.env
file.In my case, I didn't use the
recaptcha
gem. This is a route you can take. For educational purposes, I chose to verify the token manually.
def validate_recaptcha
recaptcha_secret_key = ENV['SERVER_RECAPTCHA_KEY']
recaptcha_response = params['captcha']
uri = URI('https://www.google.com/recaptcha/api/siteverify')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request.set_form_data(
'secret' => recaptcha_secret_key,
'response' => recaptcha_response
)
response = http.request(request)
if response.is_a?(Net::HTTPSuccess)
data = JSON.parse(response.body)
return data['success']
else
return false
end
end
use_ssl
to truesuccess
value from the response bodydef some_form_method
if validate_recaptcha
# ... do something with the form data
else
# ... reject the form submission as the reCAPTCHA token is invalid
end
end
You should now have a working reCAPTCHA on a React form with verification in your backend. Your form is now protected from spam messages and bots. Voilá!
Ruby on rails
React
Frontend