Submit forms without using re-captcha
Tags: react, javascript, nextjs
Published: 2020-09-24 14:02:03

If you ever had to put a form on a public page, you know bots will find it and send you "service offerings", newsletters, direct emails, and much more. That's exactly what happened the first time I deployed the Ingenious contact form.

Opening a gateway to an inbox is problematic because, like everything in security, attackers -spammers in this case- have all the time in the world, and they can afford to do it wrong a million times until they nail it.

To fix this problem, some developers use re-captcha, a tool that _"[...]uses an advanced risk analysis engine and adaptive challenges to keep malicious software from engaging in abusive activities on your website"_ 🥱. In plain English it **keeps bots away from your forms**.

There are a lot of great wrappers depending on the technology you are using. At Ingenious, we use Next.js and deploy our website to Vercel. If I wanted, I could have implemented some re-captcha validation on our contact form with an already existing npm package, but the sole idea of adding a library for something that trivial didn't sound right.

Looking for alternatives, I learned about honeypots. Honeypots are additional inputs you put on a form to make bots think they are submitting correct info. The idea is to give the bot a honeypot field that looks legit and hide it with CSS from the users. On the backend, we can check if honeypot fields were submitted and discard that submission.

```jsx
export default function ContactForm({ onSubmit }) {
  return (
    <div>
      <h1>Contact Us</h1>
      <form onSubmit={onSubmit}>
        {/* This is for the bot */}
        <div className="honey">
          <label htmlFor="name">Name</label>
          <input id="name" name="name" type="text" autoComplete="off" />
        </div>
        <div className="honey">
          <label htmlFor="email">Email</label>
          <input id="email" name="email" type="email" autoComplete="off" />
        </div>
        <div className="honey">
          <label htmlFor="message">Message</label>
          <textarea id="message" name="message" autoComplete="off"></textarea>
        </div>

        {/* This is for real users */}
        <div>
          <label htmlFor="name89jhbg2">Name</label>
          <input name="name89jhbg2" id="name89jhbg2" type="text" />
        </div>
        <div className="flex flex-col">
          <label htmlFor="email789miu82">Email</label>
          <input name="email789miu82" id="email789miu82" type="email" />
        </div>
        <div className="flex flex-col">
          <label htmlFor="message342cdssf3">Message</label>
          <textarea name="message342cdssf3" id="message342cdssf3"></textarea>
        </div>
        <button>Send</button>
      </form>
      <style jsx>{`
        .honey {
          display: none;
        }
      `}</style>
    </div>
  )
}
```

Another technique I've used is to delay the rendering of the form several seconds after the page itself is rendered. My thinking behind this is that bots may or may not execute JS -likely they do- but I don't think they will wait more than 3 or 4 seconds. On the other hand, users don't need to see the form until they are way down on the page -the contact form in our case is close to the bottom of the page. By the time user has scrolled to the bottom, the form will be loaded already.

When working with Next.js, you will use the `next/dynamic` package that's somehow similar to the `React.lazy` functionality. The idea is to dynamically `import` a module creating a new chunk. Next.js will then fetch the module at runtime.

Importing a module returns a promise that we can delay. In the case of Next.js, we need to ask for the module to be client-side only with `ssr: false`, otherwise it will end up on the statically generated page.

```jsx
import dynamic from "next/dynamic";
import { delay } from "../utils";

const ContactForm = dynamic(
  () => import("../components/contact-form").then(delay(3000)),
  {
    ssr: false
  }
);

export default function IndexPage() {
  return (
    <>
      <ContactForm onSubmit={onSubmit} />
    </>
  );
}
```

Lastly, we can tell Next.js to use a placeholder component while loading the dynamically imported one.

```jsx
import dynamic from "next/dynamic";
import { delay } from "../utils";

function ContactFormPlaceholder() {
  return <div>Nice Spinner</div>;
}

const ContactForm = dynamic(
  () => import("../components/contact-form").then(delay(3000)),
  {
    ssr: false,
    loading: () => <ContactFormPlaceholder />
  }
);
```

This technique may hurt SEO, but how many times we need SEO for a contact form? The whole point is to allow real users to submit the form, not bots, even GoogleBot. 

## Here's the full example

You can reload the codesandbox, and scroll down to the bottom to se the form placeholder before the actual form loads, and click the "Show hidden fields" checkbox to try submitting the form as a bot.

{% codesandbox contact-form-p9u6b %}