Honeypot

Modern web applications are often bombarded with automated spam bots that try to submit forms with irrelevant or harmful content. There are a number of reasons they do this:
  • To post their links to your site, hoping to increase their search engine ranking
  • To test your site for vulnerabilities
  • To help distribute malware
This not only degrades the user experience but can also strain server resources and contaminate databases with unwanted data.
On my own site I found that spam bots were submitting my login and contact forms which resulted in sending emails to myself and random people which was really annoying and affected my deliverability. The nice thing is that it's pretty cost prohibitive for people to make sophisticated spam bots, so a few simple tricks can help you avoid most of the spam. To defend against these bots, you can use a common strategy known as a honeypot field.
A honeypot field is a form input that's designed to be invisible to genuine human users but enticing to bots. The concept behind it is simple: since bots will often fill out every available field in a form, we can trick them by adding a field that human users can't see (and therefore won't fill out). If any submissions contain data in that hidden field, it's a strong indicator that the submission is from a bot, allowing us to discard or flag the submission accordingly.
Another useful thing you can do along with the honeypot field is to add a field that will allow you to determine when the form was generated. So if the form is submitted too quickly, you can be pretty confident that it's a bot. This is more tricky than it sounds because bots could easily change the value of that field, so you do need to encrypt the value. But if you manage that, it's a great way to catch bots.
To implement a honeypot field, you'd typically add an input field to your form and then hide it using CSS. It's crucial that this field is hidden in a way that humans can't use, but a bot will. It's also a good idea to give the honeypot field a name that sounds enticing to bots, like "url" or "email", which can make it even more likely they'll fill it in. The trick is making sure the field doesn't conflict with any other fields in your form. So you can sometimes append __confirm or something similar to the field to keep it unique and the bot will still fill it in.
For example:
<form>
	<label>
		Name:
		<input type="text" name="name" />
	</label>
	<label>
		Email:
		<input type="email" name="email" />
	</label>
	<label>
		Website:
		<input type="text" name="url" />
	</label>
	<label>
		Message:
		<textarea name="message"></textarea>
	</label>
	<div style="display: none;">
		<label>
			Do not fill out this field
			<input type="text" name="name__confirm" />
		</label>
	</div>
	<button type="submit">Send</button>
</form>
When the form is submitted, you can then check the honeypot field on the server-side. If it has a value, it's a strong sign that the submission is automated and can be safely ignored or flagged.
Honeypot fields offer a non-intrusive and user-friendly approach to combating spam. Unlike CAPTCHAs, which can sometimes frustrate genuine users, honeypots operate silently in the background. However, it's worth noting that no solution is foolproof. As bots become more sophisticated, they might learn to avoid honeypots. Additionally, if you're targeted by a specific group then they'll be able to code their bot specific to your protections.
So the idea of a honeypot field is just to keep yourself out of the "low hanging fruit" category.
As an example, on my website, I have honeypot fields implemented on my public fields and I log out when honeypot fields are filled in. Here's some logs I saw just today (as I write this):
FAILED HONEYPOT {
	formId: 'newsletter',
	firstName: '**********',
	email: '***********@gmail.com',
	convertKitTagId: '',
	convertKitFormId: '827139',
	url: 'Too MUCH'
}
POST kentcdodds.com/action/convert-kit 200 - 41.616 ms
FAILED HONEYPOT ON LOGIN {
	email: '***********@gmail.com',
	password: '***********'
}
POST kentcdodds.com/login 200 - 34.213 ms
I'm using url for the honeypot field of the first form and password as the honeypot field for the second form (my site's login is passwordless). I've saved myself actual money (and improved my email deliverability) because of this feature so it's definitely one you'll want to implement for all your publicly facing forms.
Remember, it's always a cat and mouse game between spammers and developers. As one side innovates, so does the other. Stay updated, and be ready to iterate on your solutions as the digital landscape evolves.

Remix Utils

This is why using a library that can be continuously updated is a great approach. remix-utils has a great solution to this problem which we'll be using in this exercise.
// create the honeypot instance:
const honeypot = new Honeypot()

// get the props for our fields:
const honeyProps = honeypot.getInputProps()

// pass those to the React provider
<HoneypotProvider {...honeyProps}>
	<App />
</HoneypotProvider>

// render the fields within our form
<HoneypotInputs />

// check the honeypot field on the server
honeypot.check(formData)