Logging Failed Logins

A simple Plugin to log failed login attempts.

By Bob Ray  |  April 16, 2024  |  5 min read
Logging Failed Logins

A MODX Forum user asked for a way to log failed logins when a user complained that they were entering the correct credentials and sometimes not getting in. Here's a small Plugin that will write information to the error when a login fails.

Be sure to read the section below entitled Privacy and Security Isues!

The Plugin would also be useful for seeing if hackers are trying to log in to your site. It allows you to look at their attempts. You could add rules in your .htaccess file based on the strings they use to block them before they get anywhere near the site.

The Plugin

This Plugin (we'll call it FailedLogin, though the name is arbitrary) should be connected to the OnBeforeWebLogin event, and also to OnBeforeManagerLogin if you want to log failed Manager logins. Just check one or both of those events on the System Events tab when editing the Plugin. Be sure to save the Plugin after checking them.

Here's the code:

$loginUser = $modx->getObject('modUser', array('username' => $username));
$msg = '';
if (! $loginUser) {
    /* User not found */
    $msg = "[FailedLogin Plugin] - User not found: {$username}";
} else {
    /* User Found, check password */
    /* Don't log admin credentials!!! */
    if ($username == 'yourusernamehere') {
        return true;
    }
     if (!$loginUser->passwordMatches($password)) {
         /* Password is wrong */
         $msg = "[FailedLogin Plugin] - invalid password for user: {$username}";
     } else {
         /* Login will be successful. Uncomment the next line to see
            successful logins */
         // $msg = "[FailedLogin Plugin] - Successful login for user: {$user}";
     }

}

if (!empty($msg)) {
   $modx->log(modX::LOG_LEVEL_ERROR, $msg);
}

/* Make sure we don't interfere with the regular login process */
return true;

Options

The code to log successful logins is commented out in the code above. You can uncomment it to add successful logins to the log.

In the next three examples, the code would replace this code in the Plugin:

if ($username == 'yourusernamehere') {
return true;
}

You could also change the section that prevents the admin credentials from being logged to this inorder to prevent logging any administrator credentials (recommended):

if ($loginUser->isMember('Administrator')) {
    return true;
}

If you only want to log brute-force hacking attempts, you can eliminate all members of user groups from the log with this code:

/* List all your user groups here */
$groups = array(
    'Administrator',
    'Editor',
    'ForumMembers',
);
if ($loginUser->isMember($groups)) {
    return true;
}

Do not include the (anonymous) user group in the list. Add the code carefully, if you create a syntax error, the Plugin may crash and prevent anyone (including you) from logging in.

To skip the report for all existing site users, add this line after the getObject() call at the top (note that this doesn't replace any code, it just added near the top of the Plugin):

if ($loginUser) {
    return true;
    }

Disaster Recovery

If you make a mistake entering the code and are unable to log in, use PhpMyAdmin in cPanel to disable the Plugin. Select the MODX database. Find the Plugin in the modx_site_plugins table, change the value of the disabled field from 0 to 1, then click on the "Go" button. Delete all files in the core/cache directory before trying to log in.

Privacy and Security Issues

The code above captures and logs usernames. It could also log the given passwords (which may or may not be correct). Logging the passwords would violate your users' expectations and their privacy. That's why most sites will create, or let users create, a new password, rather than exposing their current one. Usernames, on the other hand, are already available to any Manager user who has the right permission or has access to the database.

Important! It's also worth noting that the error log is a plain-text file on the server (core/cache/logs/error.log). In insecure installations, it can be viewed simply by going to http://yoursite.com/core/cache/logs/error.log. Be sure that it is protected from prying eyes, preferably by moving the MODX core above the web root in MODX 2 where it can't be accessed by outside users. In MODX 3, you can't move the core, so you need to restrict access to that directory in .htaccess as described below.

You can place the log messages in a custom log in another location with code like this:

$log_target = array(
    'target'=>'FILE',
    'options' => array(
        'filepath' => 'path/to/directory/',
        'filename'=>'my_custom.log',
    )
);

$modx->log(modX::LOG_LEVEL_ERROR, $msg, $log_target);

The filepath must end in a slash, but the location can be anywhere as long as the directory exists and is writable. In MODX 3, where the core must not be moved, you can set the custom log path to somewhere outside the web root.

Another solution is to leave the error log where it is, rename the ht.access file in the MODX core directory to .htaccess. It should already contain code that blocks access to the error log.

That will prevent users from looking at any log files remotely, though Manager users who have view_eventlog permission can view it in the Manager. And Manager users who have access to the "Files" tab and the appropriate Media Source can navigate to the error log and view it that way.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.