Re-use and migrate WordPress password hashes in October
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.
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