How to Implement Invisible Captcha with Next.js in 2022
![Erik Mellum](/assets/images/team/erik-avatar.gif)
![Erik Mellum](/assets/images/team/erik-avatar.gif)
Next.js Invisible Captcha in 2022
When I went to add Captcha to my site I was disappointed in the resources. I expected this to take about 10 minutes, and it ended taking me a few hours. I had to stitch together details from a variety of StackOverflows, Google documentation, Medium posts, and Github repo documentation. While this is fairly typical, I wanted to have a single place that contained all the useful information. This article focuses on invisible ReCaptcha. My examples are in Typescript. This article uses:
- Next.js
- React
- Dotenv
- Typescript
Overview
It's important to have a high level view of what we are doing, and how ReCaptcha works.
- We need a secret and key from ReCaptcha provider
- We need to install ReCaptcha script, we will use an npm package for this
- Add the installed package component into contact form
- Before sending our form data we generate a Captcha token. Include this token in the payload.
- Backend route will take this token and call a simple validation to tell us if the user is valid or not valid. If it's invalid we won't process the form submission.
Creating a reCaptcha Key
My reCaptcha kept returning false due to "invalid input secret". I was able to locate a StackOverflow that pointed out something opaque. Your keys must come from https://www.google.com/recaptcha/admin/create but not https://console.cloud.google.com/security/recaptcha. Are you kidding? Go ahead and create a secret and a key at the correct one.
Add key and secret to your .env file
If you don't have dotenv set up yet, go ahead and just hardcode these in for your testing. Once you get them working, find a nice article somewhere that teaches you how to use dotenv. Or just examine the source code in the next.js example with dotenv.
# CAPTCHA RECAPTCHA_KEY="key" RECAPTCHA_SECRET="secret"
Install react-google-captcha
Go install react-google-captcha. This package made installation of captcha more convenient than anything on the official documentation. Just use import ReCAPTCHA from "react-google-recaptcha";
.
Add invisible ReCaptcha to your ContactForm component
This is as simple as it gets. Invisible ReCaptcha uses your submit button to generate the Captcha token. This is obviously much more convenient for a user than a ReCaptcha checkbox.
const recaptchaRef: any = React.createRef(); ... <ReCAPTCHA ref={recaptchaRef} size="invisible" sitekey={process.env.RECAPTCHA_KEY!} />
My submit button looks like this (but yours can look different). This is included as an example, but not strictly important for the tutorial.
<Button type="submit" className='mt-2 w-full inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-sky-700 hover:bg-sky-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-600 sm:w-auto h-12' onClick={submit} text="Submit" state={state as any} />
Generate captcha token on form submission
I define my payload in the submit
onClick handler. I included the whole function although only the
const captcha = await recaptchaRef.current.execute();
is relevant. Also, the documentation for react-google-recaptcha claimed that you should use executeAsync here, but it broke everything when I did that. This worked fine.
const submit = async (e: MouseEvent) => { e.preventDefault(); setState('Loading') try { const captcha = await recaptchaRef.current.execute(); await axios.post('/api/contact', { firstName, lastName, email, phone, subject, message, captcha, communicationMethod }) setState('Success') setEmail('') setModalOptions(successOptions) // window.grecaptcha.reset(); } catch (e: any) { setState('Error') setModalOptions({ title: 'Unable to send message.', description: e.response.data.error, action: 'Okay', icon: <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100"> <ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true" /> </div>, setOpen: setShowModal, }); } finally { setShowModal(true); } }
Add a helper function for validateCaptcha
This was one of the areas that I was most disappointed in the internet. I was thankful to have code examples to work off of, but as far as syntax goes they were basically trash (no offense internet). I also have two forms that I want to use Captcha on, so extracting captcha validation to a helper function is the right choice. Let's actually do this using modern es6 syntax:
import axios from 'axios' export default async function validateCaptcha(response_key: string) { const secret_key = process.env.RECAPTCHA_SECRET const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${response_key}` try { const response = await axios.post(url) return response.data.success } catch (error) { console.error(`Error validating captcha: ${error}`) return false } }
Use our helper function in the backend to validate Captcha
Place this block right at the top of your submit handler in the api.
import { NextApiRequest, NextApiResponse } from 'next' import validateCaptcha from '../shared/components/validateCaptcha'; export default async function Contact(req: NextApiRequest, res: NextApiResponse) { const { captcha } = req.body; console.log('captcha', captcha); const validCaptcha = await validateCaptcha(captcha); if (!validCaptcha) { return res.status(422).json({ error: "Unprocessable request, Invalid captcha code.", }); } ... };
Wrapping up
Now you can test. If something goes wrong here is the list of resources that I used to create this article and troubleshoot when I had problems. Good luck hunting bots! Try using the chat widget if you want to talk about a more specific problem and who knows. Maybe a human will respond (no promises).
- https://github.com/dozoisch/react-google-recaptcha
- https://stackoverflow.com/questions/1264703/google-recaptcha-keep-getting-incorrect-captcha-sol
- https://stackoverflow.com/questions/36939870/google-recaptcha-returns-false-due-to-invalid-input-secret
- https://stackoverflow.com/questions/54695089/invalid-input-response-and-secret-when-verifying-google-recaptcha
- https://stackoverflow.com/questions/46514194/how-to-reset-google-recaptcha-with-react-google-recaptcha?rq=1
- https://stackoverflow.com/questions/67935841/recaptcha-v3-error-incorrect-captcha-sol-randomly
- https://stackoverflow.com/questions/1264703/google-recaptcha-keep-getting-incorrect-captcha-sol
- https://stackoverflow.com/questions/27133172/recaptcha-documentation-unclear-and-cross-site-error-testing-it
- https://stackoverflow.com/questions/3232904/using-recaptcha-on-localhost*
From the blog
Come see what we're up to. We are excited to add new content regularly. We write content about new technologies, new products, and new business opportunities.
Test Deployment
Erik Mellum
Do you embrace change or fear it?
Erik Mellum
Six reasons dropbox paper is better than notion
Erik Mellum
Why you should estimate development tasks not stories
Erik Mellum
How to grow your career as a software developer
Erik Mellum
Resource based planning for product and engineering teams
Erik Mellum
Tips and pitfalls of setting up a monorepo with Turbo, Nextjs, and Vercel
Erik Mellum
Best practices any team can use with their typescript projects
Erik Mellum
How to Implement Invisible Captcha with Next.js in 2022
Erik Mellum
Where Do Computer Viruses Come From?
Erik Mellum
Rune Launches in Chico
Erik Mellum
Why Do Computers Break?
Erik Mellum