October CMS resources and help articles

Simple and to the point. Optimized by the community.

Re-use and migrate WordPress password hashes in October

5
by OFFLINE, last modified on February 12th, 2020

This trick describes how you can migrate existing WordPress users to October. It allows a user to log in to the October backend with the same password that was used on WordPress. It will migrate the password to a proper bcrypt hash during the first login.

Step 1: Import user data

Migrate all WordPress password hashes (ẁp_users.user_pass) into the backend_users table. I won't describe in detail how you do this (for example by using Corcel). Just make sure that the wp_users.user_pass column ends up in a new backend_users.wp_password_hash column that you have added manually (or via a proper plugin migration).

The backend_users table should look like this (some columns omitted):

id login password wp_password_hash
1 admin null or random string $1$fDzVfXkk$XY7EKilSen7g8xtVFwyjD.
2 otheruser null or random string $1$Z9NUyZha$OO3qctee2.bZqfnEK3g5J1

Step 2: Install mikemclin/laravel-wp-password

To check a plain text password against a stored WordPress password hash, we can use the mikemclin/laravel-wp-password composer package.

Install it by running composer require "mikemclin/laravel-wp-password" "~2.0.1".

Make sure to run this command from your October root directory, not a plugin's subdirectory. Otherwise you will end up with mixed versions of various Illuminate/* packages.

If you need to add the dependency to a specific plugin make sure it's included in the plugin's composer.json but always run composer install in the root directory.

Step 3: Extend the AuthManager

To extend the default AuthManager class that October uses during the login process, add the following code to a Plugin's Plugin.php file:

use YourVendor\YourPlugin\Classes\AuthManager;

class Plugin extends PluginBase
{
    public function register()
    {
        App::singleton('backend.auth', function () {
            // This overrides the default AuthManager instance with our own.
            return AuthManager::instance();
        });
    }
}

Step 4: Override AuthManager methods

There are multiple ways to hook into October's AuthManager. For this use-case, it is enough to override the findUserByCredentials method in our custom AuthManager class.

Create the following class and save it to plugins/your-vendor/your-plugin/classes/AuthManager.php

<?php

namespace YourVendor\YourPlugin\Classes;

use Backend\Classes\AuthManager as BackendAuthManager;
use MikeMcLin\WpPassword\Facades\WpPassword;
use October\Rain\Auth\AuthException;

class AuthManager extends BackendAuthManager
{
    public function findUserByCredentials(array $credentials = [])
    {
        $originalException = null;
        try {
            // Try to log in using the base class' logic. If no exception is thrown,
            // the login worked with the provided credentials.
            // In these case there's nothing for us to do.
            return parent::findUserByCredentials($credentials);
        } catch (AuthException $e) {
            // Capture the thrown exception. We will re-throw it if we are unable
            // to re-hash the user's password.
            $originalException = $e;
        }

        // Find the user by the provided login attribute. 
        // Re-throw the original exception if no user is found.
        $user = $this->findUserByLogin($credentials['login']);
        if ( ! $user) {
            throw $originalException;
        }

        // Check if the user model contains an old wp_password_hash.
        // If not, we have nothing to check against and the login should 
        // fail with the original exception.
        if ( ! $oldHash = $user->wp_password_hash) {
            throw $originalException;
        }

        // Check if the provided password matches the stored WordPress hash
        // using WpPassword's check method.
        // If it does not match, abort by re-throwing the original exception.
        if ( ! WpPassword::check($credentials['password'], $user->wp_password_hash)) {
            throw $originalException;
        }

        // We have found a user where the WordPress hash matched. Let's re-hash the password
        // to bcrypt by setting it on the model (as password and confirmation). 
        // Also remove the old WordPress hash to make sure it is gone for good.
        $user->password = $credentials['password'];
        $user->password_confirmation = $credentials['password'];
        $user->wp_password_hash = '';
        $user->save();

        // Return the updated user model. This makes sure the usual 
        // login logic is executed.
        return $user;
    }
}

Step 5: Test the new AuthManager logic

To test the new AuthManager logic, empty the password column of a user in the backends_user table. Add a WordPress password hash to the wp_password_hash column. You can generate a hash for a specific password using a WordPress Password Hash Generator.

If you log in now, you should notice no changes to the usual login routine. If you check your database after the login the wp_password_hash column should be empty and the password column should contain a valid bcrypt hash.

Make sure to test that subsequent logins with the new hash work as expected.

Discussion

1 comment

0
seif
Post on November 25th, 2021 6:56 PM

Everytime I try to authenticate, I get this error: "Target [MikeMcLin\WpPassword\Contracts\WpPassword] is not instantiable." on line 978 of /shared/httpd/project/vendor/laravel/framework/src/Illuminate/Container/Container.php

We use cookies to measure the performance of this website. Do you want to accept these cookies?