The WordPress login is quite possibly secure enough, but on a couple of my sites I’ve added a piece of custom authentication. This also acts as a bot-filter, given that the required input (in theory) isn’t obvious.
I figured it might be useful to describe the process of adding a field to the login form, and subsequently validating the user’s input.
A note – the algorithm I’ve used on my other sites isn’t the same as this, and doesn’t depend on a session variable. Sessions are by default unavailable in WordPress, which strives to be stateless, but you can set up a session quite easily, as it turns out.
Another note – there are plugins that will do something similar, but I like to have more control over my sites, and it’s not difficult to code.
The idea is that I create a string containing pairs of characters and digits. For example, this might be p4s1g2d4b6c7m4t3. I display this on the login page. My users know the secret, which is that they must enter the digits following ‘c’ and ‘p’ respectively. In this case, they would enter 74.
The code is regenerated on every login form, in an attempt to make it tricky to see what’s going on. It’s not utterly fool proof but hopefully obscure enough to ward off casual intruders.
Step 1 – create and store a random ‘captcha’ value, and add a field to the login form.
This code would go in your theme’s functions.php or in a plugin. The PHP tags at start and end are only shown here to enable syntax highlighting.
<?php
add_action( 'login_form', 'my_login_form');
function my_login_form () {
session_start();
$chars = ['b','c','d','g','m','p','s','t'];
for ($i = 0; $i < 8; $i++) {
$chars[$i] .= rand(1,9);
}
shuffle($chars);
$_SESSION['captcha'] = join('',$chars);
?>
<div style="height: 100px;">
<label>
<p><?php echo $_SESSION['captcha']; ?></p>
<p>Enter security code numbers below</p>
<input type="text" name="captchacode" value="">
</label>
</div>
<?php }
So we hook into WP’s login_form, in which you are simply adding to the standard form. We start a session, create the random string, and store it in the session. Then we display the string, and add an input field to the form.
Step 2 – validate the user’s input against the stored string.
Again, this code would go in your theme’s functions.php or in a plugin.
<?php
add_filter('authenticate', 'check_captcha', 90, 3);
function check_captcha($user, $login, $password) {
session_start();
$session_code = $_SESSION['captcha'];
session_destroy();
if ($login == '' or $password == '') return $user;
if (is_wp_error($user)) {
$error = '<strong>ERROR</strong>: Login details not recognised';
return new WP_Error( 'invalidcombo', $error );
}
$captcha = '';
$chars = ['c','p'];
foreach ($chars as $char) {
$i = strpos($session_code, $char);
$captcha .= $session_code[$i + 1];
}
$input = sanitize_text_field($_POST['captchacode']);
if ($captcha != $input) {
$error = '<strong>ERROR</strong>: Login details not recognised';
return new WP_Error( 'invalidcombo', $error );
}
return $user;
}
?>
We hook into WP’s authenticate filter, and pick up the session variable we stored earlier. Then just to be tidy, we destroy the session.
Next I have some code to check if WP has already have rejected the user, so there’s no point in continuing beyond there; we just return an error.
And then on to the check for the extra input field. The two characters to check are set up in an array (‘c’ and ‘p’ in this case). If you wanted to add more characters this is where you’d do it.
Then we concatenate the digits to the right of each of the key characters, and check the user’s input against the result. If it’s no good, we return an error, but if it looks good, we return the value that was originally supplied to the filter and let WP do the rest.