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', ], ],