Eoghan O'Brien
husband · father · developer at Brightspot · lead engineer on Ignite
Home

Custom user providers to authenticate legacy passwords

We recently ported a legacy application written in Zend 1.x over to Laravel 5.7 and we had a few issues, which is fairly typical when dealing with legacy codebase transformations. In our case, one problem was allowing existing users to log in with a password which had been encrypted using a different, less secure hashing technique, SHA-1. In Laravel, we use the bcrypt function to hash passwords, so how do we authenticate a password using both encryption types.

First thing to do is create the UserProvider class, in order to hook into the Authentication layer in the way that Laravel expects, we do this by implementing \Illuminate\Contracts\Auth\UserProvider:

<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider
{
    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function updateRememberToken(Authenticatable $user, $token);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(Authenticatable $user, array $credentials);
}

In our case, we just want to check if the password was hashed using the typical Laravel bcrypy hash first, otherwise we'll check for SHA-1. We still want to use the regular \Illuminate\Auth\EloquentUserProvider class so let's inherit from it and override the validateCredentials() method.

<?php

namespace MyPackage\Auth;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;

class ZendUserProvider extends EloquentUserProvider
{
    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        if (parent::validateCredentials($user, $credentials)) {
            return true;
        }

        $salt = config('auth.legacy.salt');
        $hash = sha1($salt . $password . $user->salt);

        if ($hash == $user->password) {
            return true;
        }

        return false;
    }
}

Zend 1.x maintains both an application password salt and a per user salt. We need to work that into our hash in order to test the password. If the hashed and salted password matches what's in the database, we authenticate as normal otherwise, we'll just return false and let the Auth guard deal with the rest.

With our UserProvider class ready, we need to tell Laravel how to find it, we can do this by registering the UserProvider via the AuthServiceProvider:

Auth::provider('zend', function ($app, array $config) {
    return new ZendUserProvider($config);
});

Next, we need to update our config/auth.php file. Under the providers key, add the new provider:

'providers' => [
    'users' => [
        'driver' => 'zend',
    ],
]

Finally, under the guards key, we need to tell our guard to use our custom provider:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],