Preventing automated sign-ups

The Session goes through periods of getting spammed with automated sign-ups. I’m not sure why. It’s not like they do anything with the accounts. They’re just created and then they sit there (until I delete them).

In the past I’ve dealt with them in an ad-hoc way. If the sign-ups were all coming from the same IP addresses, I could block them. If the sign-ups showed some pattern in the usernames or emails, I could use that to block them.

Recently though, there was a spate of sign-ups that didn’t have any patterns, all coming from different IP addresses.

I decided it was time to knuckle down and figure out a way to prevent automated sign-ups.

I knew what I didn’t want to do. I didn’t want to put any obstacles in the way of genuine sign-ups. There’d be no CAPTCHAs or other “prove you’re a human” shite. That’s the airport security model: inconvenience everyone to stop a tiny number of bad actors.

The first step I took was the bare minimum. I added two form fields—called “wheat” and “chaff”—that are randomly generated every time the sign-up form is loaded. There’s a connection between those two fields that I can check on the server.

Here’s how I’m generating the fields in PHP:

$saltstring = 'A string known only to me.';
$wheat = base64_encode(openssl_random_pseudo_bytes(16));
$chaff = password_hash($saltstring.$wheat, PASSWORD_BCRYPT);

See how the fields are generated from a combination of random bytes and a string of characters never revealed on the client? To keep it from goint stale, this string—the salt—includes something related to the current date.

Now when the form is submitted, I can check to see if the relationship holds true:

if (!password_verify($saltstring.$_POST['wheat'], $_POST['chaff'])) {
    // Spammer!
}

That’s just the first line of defence. After thinking about it for a while, I came to conclusion that it wasn’t enough to just generate some random form field values; I needed to generate random form field names.

Previously, the names for the form fields were easily-guessable: “username”, “password”, “email”. What I needed to do was generate unique form field names every time the sign-up page was loaded.

First of all, I create a one-time password:

$otp = base64_encode(openssl_random_pseudo_bytes(16));

Now I generate form field names by hashing that random value with known strings (“username”, “password”, “email”) together with a salt string known only to me.

$otp_hashed_for_username = md5($saltstring.'username'.$otp);
$otp_hashed_for_password = md5($saltstring.'password'.$otp);
$otp_hashed_for_email = md5($saltstring.'email'.$otp);

Those are all used for form field names on the client, like this:

<input type="text" name="<?php echo $otp_hashed_for_username; ?>">
<input type="password" name="<?php echo $otp_hashed_for_password; ?>">
<input type="email" name="<?php echo $otp_hashed_for_email; ?>">

(Remember, the name—or the ID—of the form field makes no difference to semantics or accessibility; the accessible name is derived from the associated label element.)

The one-time password also becomes a form field on the client:

<input type="hidden" name="otp" value="<?php echo $otp; ?>">

When the form is submitted, I use the value of that form field along with the salt string to recreate the field names:

$otp_hashed_for_username = md5($saltstring.'username'.$_POST['otp']);
$otp_hashed_for_password = md5($saltstring.'password'.$_POST['otp']);
$otp_hashed_for_email = md5($saltstring.'email'.$_POST['otp']);

If those form fields don’t exist, the sign-up is rejected.

As an added extra, I leave honeypot hidden forms named “username”, “password”, and “email”. If any of those fields are filled out, the sign-up is rejected.

I put that code live and the automated sign-ups stopped straight away.

It’s not entirely foolproof. It would be possible to create an automated sign-up system that grabs the names of the form fields from the sign-up form each time. But this puts enough friction in the way to make automated sign-ups a pain.

You can view source on the sign-up page to see what the form fields are like.

I used the same technique on the contact page to prevent automated spam there too.

Have you published a response to this? :

Responses

Manton Reece

Here’s what the new prompt for email newsletter subscriptions looks like for your blog. The extra step lets us generate some random values hidden from real users, inspired by this post from Jeremy Keith, to make it a tiny bit harder for bots. Could do more later now that this is in place.

7 Likes

# Liked by Royce Williams on Monday, September 30th, 2024 at 4:12pm

# Liked by Chris Silverman 🌻 on Monday, September 30th, 2024 at 4:44pm

# Liked by Jason Crowther on Monday, September 30th, 2024 at 5:10pm

# Liked by Trey on Monday, September 30th, 2024 at 7:13pm

# Liked by Thomas Vander Wal on Tuesday, October 1st, 2024 at 12:53am

# Liked by Benjamin on Tuesday, October 1st, 2024 at 7:45am

# Liked by felipe on Tuesday, October 1st, 2024 at 12:20pm

Related posts

Train coding

Generating a static copy of The Session from the comfort of European trains.

Securing client-side JavaScript

Tightening up my content security policy.

My approach to HTML web components

Naming custom elements, naming attributes, the single responsibility principle, and communicating across components.

Secure tunes

Closing a security hole on The Session.

Progressive disclosure with HTML

The `details` element is like the TL;DR of markup.

Related links

CSS Form Control Styling Level 1

This looks like a really interesting proposal for allowing developers more control over styling inputs. Based on the work being done the customisable select element, it starts with a declaration of appearance: base.

Tagged with

UI Pace Layers - Jim Nielsen’s Blog

Every UI control you roll yourself is a liability. You have to design it, test it, ship it, document it, debug it, maintain it — the list goes on.

It makes you wonder why we insist on rolling (or styling) our own common UI controls so often. Perhaps we’d be better off asking: What are the fewest amount of components we have to build to deliver value to our users?

Tagged with

una.im | Updates to the customizable select API

It’s great to see the evolution of HTML happening in response to real use-cases—the turbo-charging of the select element just gets better and better!

Tagged with

SCALABLE: Save form data to localStorage and auto-complete on refresh

When I was in Amsterdam I was really impressed with the code that Rose was writing and I encouraged her to share it. Here it is: drop this script into a web page with a form to have its values automatically saved into local storage (and automatically loaded into the form if something goes wrong before the form is submitted).

Tagged with

An example of an HTML Web Component | Go Make Things

Another example of an HTML web component from Chris, who concludes:

Web Components are rapidly becoming my preferred way to add progressive enhancement to HTML elements.

Tagged with

Previously on this day

3 years ago I wrote Supporting logical properties

Using the CSS trinity of feature queries, logical properties, and unset.

4 years ago I wrote Twenty years of writing on my website

This online journal is two decades old.

9 years ago I wrote Someday

Changing defaults in browsers …someday.

14 years ago I wrote Ten

Happy birthday to this.

16 years ago I wrote Culchavulching

In Brighton, no-one can hear you geek out.

16 years ago I wrote Full Frontal

Be in Brighton on November 20th.

17 years ago I wrote Reading immaterial

Something for your digital bookshelf.

17 years ago I wrote Automata

Carla Diana talks about robots at Flash on the Beach in Brighton.

18 years ago I wrote Kung Shui

Ajax and accessibility; a presentation transcribed.

19 years ago I wrote Sydney to Melbourne

It’s time for me to expand my Australian horizons.

19 years ago I wrote Wrapping up Web Directions South

Two thumbs up from me.

20 years ago I wrote He comes from a land down under

The podcasts and the photos have started coming in from Web Essentials in Syndey. Oh, how I wish I could be there!

22 years ago I wrote WiFi Regained

Remember how I was saying that the wireless reception in my iBook went all screwy a while back? Well, I sent the iBook off to Apple so that they could have a look at it.

23 years ago I wrote Anniversary

I have been blogging now for exactly one year.

23 years ago I wrote dooce

Rejoice! For Heather Hamilton is back.

23 years ago I wrote JCPenney

Jessica refuses to believe that JCPenney are actually selling the "Forward Command Post" model in their catalogue:

24 years ago I wrote Welcome to my world

This is my first entry in my first online journal.