Migrating WordPress users and passwords to Laravel

I recently needed to migrate users from a WordPress SaaS application to a Laravel SaaS application.

The SaaS in question is: Simplehours – take a look.

As part of the migration I needed to move the users across from the WordPress user database table to the new database used by Laravel. Let’s look at how we migrate users from one system to the other as seamless as possible.

Overview

We want to transfer across the user details and the user passwords. User details are fairly simple, however passwords present a complication.

WordPress user accounts have their passwords hashed using MD5 whereas Laravel uses Bcrypt. We cannot just convert MD5 to Bcrypt because MD5 cannot be de-crypted to read the user’s password and then re-hash.

What we can do is add the old MD5 WP password and then check for this and update them to the latest BCrypt password. At first I decided to add a whole new column, but as I started developing I realised there was no need, so just added these WP passwords to the existing passwords column.

Step 1. Import wp_users into Laravel database

There are a number of ways to do this. I’m a fan of SequelPro, so for me I’ll open up the WordPress database using this application, select the wp users table and export.

Then head to our Laravel database and import. SequelPro does have the option to import directly to the laravel user database (called users) but I want to do a bit more with our data, so don’t choose this option.

Let’s look at the WordPress and Laravel database table design:

Table: wp_users 
ID
user_login
user_pass
user_nicename
user_email
user_url
user_registered
user_activation_key
user_status
display_name
Table: users
id
name
email
password
created_at
updated_at
public_id

We will need to copy the following columns over: name, email, password, registered date time. Rather than creating a new column for the wp_passwords we will use the existing passwords column. We will just need to amend it to be nullable in case there are empty passwords.

This can be done using Laravel Migrations. Run the following artisan command in the console to create our migration table:

php artisan make:migration update_users_table_for_wp --table=users

This will create a new migration file in our database/migrations folder. In side this new file we can add the following:

 // create the new column for wp passwords and update passwords col
        Schema::table('users', function (Blueprint $table) {
            $table->string('password')->nullable()->change();
        });

We can then use the Laravel Query Builder to create some code to insert new rows in the users table from the wp users table. This can be added in the same migrations file and will be run when the migration is run.

use App\User;
use Illuminate\Support\Facades\DB;
 
    // Migrate from WP Users > Laravel Users
        DB::table('wp_users')
            ->orderBy('id')
            ->chunk(100, function ($wp_users) {
                foreach ($wp_users as $wp_user) {

                    // add new user (if doesn't exist)
                    User::firstOrCreate(
                        [
                            'email' => $wp_user->user_email,
                        ],
                        [
                            'name' => $wp_user->user_nicename,
                            'password' => $wp_user->user_pass
                        ]
                    );
                }
            });

Note we are doing two extra things here:

  1. Chunking the results which will facilitate migrations of large numbers of users.
  2. Setting the laravel password (password column) do be a hash of the WordPress password. I got this idea from this blog on migrating passwords across.

Finally we will need to add the password columns to our User Model (user.php) as ‘fillable’ so we can mass-assign values to them.

protected $fillable = [
        'name',
        'email',
        'password',
    ];

Before running the migration script, make sure you have a backup of your database.

Run this migration using the following command:

php artisan migrate

Step 2. Letting Users Log in with old WordPress passwords

Laravel uses Bcrypt to hash user passwords securely and WordPress implements the Portable PHP password hashing framework which uses MD5 hashing with a salt found in the wp-config.php file.

Fortunately, there is a package we can use to read the WordPress passwords within laravel.

Once this package is installed we can use Laravel event listeners to listen for login, compare the passwords and rewrite a new password based on the Laravel bcrypt hashing method.

This process was inspired by a similar approach taken with Drupal in this stack overflow ticket.

First we add an Event Listenerd in our Provider/EventServiceProvider.php file:

'Illuminate\Auth\Events\Attempting' => [
            'App\Listeners\WordPressPasswordUpdate',
        ],

Then in our listeners folder we can add the following WordPressPasswordUpdate.php file that contains:

class WordPressPasswordUpdate
{
    public function handle(Attempting $event)
    {

        $this->check($event->credentials['password'], \App\User::where('email', $event->credentials['email'])->first()->password ?? 'not found');
    }

    public function check($value, $hashedValue, array $options = [])
    {
        if ($this->needsRehash($hashedValue)) {

            if ($this->user_check_password($value, $hashedValue)) {

                $newHashedValue = (new \Illuminate\Hashing\BcryptHasher)->make($value, $options);
                \Illuminate\Support\Facades\DB::update('UPDATE users SET `password` = "' . $newHashedValue . '" WHERE `password` = "' . $hashedValue . '"');
                $hashedValue = $newHashedValue;
            }
        }
    }

    public function needsRehash($hashedValue, array $options = [])
    {
        return substr($hashedValue, 0, 4) != '$2y;
    }

    // WP PASSWORD FUNCTIONS
    function user_check_password($password, $stored_hash)
    {
        // $hash = md5($password);

        if (WpPassword::check($password, $stored_hash)) {
            // Password success!
            return true;
        } else {
            // Password failed :(
            return false;
        }
       
    }
}

And that is all that is required to migrate users from WordPress to Laravel and then let them use their original passwords.

Let me know how you get on in the comments and if there are any other approaches you have taken.