Hi @roffdaniel
I wasn't with enough time yesterday, so sorry for the late response.
Recently I had to connect a Laravel project to a legacy database and had some of the issues you describe (custom users table, custom hashing, etc.)
To outline a path for you I first created a new laravel project using a SQLite database, using these commands:
# create from composer
composer create-project --prefer-dist laravel/laravel test
cd test || exit
# tweak .env configuration
sed -i 's/mysql/sqlite/' .env
sed -i 's/DB_DATABASE/# DB_DATABASE/g' .env
# create sqlite database file
touch database/database.sqlite
# require auth scaffolding
composer require laravel/ui
php artisan ui bootstrap --auth
# install frontend dependencies
npm install
npm run dev
The sed commands above only replaces the .env config to use SQLite instead of MySQL. At the end my .env
file had these lines regarding the database:
DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
# DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
With the project in place I took the following steps:
1. Tweaking the User Model
First I modified the users migration that comes with Laravel to mimic your scheme:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('uLogin')->unique();
$table->string('uEmail')->unique();
$table->string('uPassword');
$table->string('uSalt');
// timestamps seemed to be saved as integers in your example
$table->unsignedInteger('uRegDate');
$table->unsignedInteger('uLastDate');
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
Then I tweaked the User model (located at ./app/User.php) to match the DB scheme:
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
// if using custom table name change this
protected $table = 'users';
// tell laravel to use these as timestamps columns
const CREATED_AT = 'uRegDate';
const UPDATED_AT = 'uLastDate';
// tell laravel to save dates unix timestamps
protected $dateFormat = 'U';
protected $fillable = [
'uLogin',
'uEmail',
'uPassword',
'uSalt',
];
protected $hidden = ['uPassword', 'uSalt'];
// Useful for default auth scaffolding
public function getAuthPassword()
{
return $this->uPassword;
}
// Useful for avoiding tweaking default auth scaffolding
public function getNameAttribute()
{
return $this->uLogin;
}
}
I added some comments above to highlight some the changes.
2. Custom user provider
As I told you in my first answer one approach is to use a Custom User Provider, for reference this is again
the docs section about it:
https://laravel.com/docs/7.x/authentication#adding-custom-user-providers
So I created a new User Provider under the project ./app directory:
<?php
namespace App;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr;
class CustomUserProvider implements UserProvider
{
public function retrieveById($identifier)
{
return User::query()->find($identifier);
}
public function retrieveByToken($identifier, $token)
{
// not implementing remeber me
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
// not implementing remeber me
}
public function retrieveByCredentials(array $credentials)
{
if (Arr::has($credentials, 'email')) {
return User::query()
->where('uEmail', $credentials['email'])
->first();
}
// allow login user either by email or name
// default auth scaffolding will use email
// but you can tweak the login view and LoginController
// to login by "login" field
if (Arr::has($credentials, 'login')) {
return User::query()
->where('uLogin', $credentials['login'])
->first();
}
return null;
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
$plain = $credentials['password'];
// use custom hash mechanism
$hash = \hash('sha256', $user->uSalt . $plain);
// comparing with hash_equals prevents time-based attacks
// see php docs about it
return \hash_equals($user->getAuthPassword(), $hash);
}
}
Then, per docs instructions, we need to register this User Provider. Let's follow the docs recommendation and
change our app's AuthServiceProvider (located at ./app/Providers/AuthServiceProvider.php)
<?php
namespace App\Providers;
use App\CustomUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot()
{
$this->registerPolicies();
// ADD THIS.
// Don't forget to import the Auth Facade and CustomUserProvider
Auth::provider('custom', function () {
return new CustomUserProvider();
});
}
}
Note that for all changed classes I removed the default comments to minimize space here.
Lastly we need to tell Laravel to use this Custom User Provider for authentication. We can do so
by modifying the ./config/auth.php file. I won't paste the whole file here, but should be easy to find
the place to change looking at the snippet below:
'providers' => [
'users' => [
'driver' => 'custom', // CHANGE HERE
// 'model' => App\User::class, // not needed anymore
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
3. Default Auth scaffolding
To test it out I used Laravel default auth scaffolding (see composer require laravel/ui above).
When registering new users we need to tweak the RegisterController
(located at ./app/Http/Controllers/Auth/RegisterController.php).
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
use RegistersUsers;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct()
{
$this->middleware('guest');
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
// might need to change the unique:users
// if using custom users table name
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function create(array $data)
{
// added for reference. use your current salt function
$salt = $this->createSalt(5);
return User::query()->create([
'uLogin' => $data['name'],
'uEmail' => $data['email'],
'uPassword' => \hash('sha256', $salt . $data['password']),
'uSalt' => $salt,
]);
}
// added for reference. use your current salt function
private function createSalt($length)
{
$text = md5(uniqid(rand(), true));
return substr($text, 0, $length);
}
}
Note this controller is made available after installing the laravel/ui package listed above
and running the php artisan ui bootstrap --auth command (bootstrap can be swapped by vue and react).
If you already have some authentication code in place take a look at the laravel/ui repository to
check out how they approach authentication.
https://github.com/laravel/ui
As we are using the Custom User Provider, after we have a user in our database, we won't need to customize
the LoginController. Authentication will just work.
4. Migrating and running
Before testing out, we need first to migrate the database, this can be done with this command:
php artisan migrate
Then we can start a webserver to test in the browser, by using this command:
php artisan serve
To test it out, follow this steps:
- Navigate to
http://localhost:8000
- Click Register on the top right
- Fill the form and register a new user, the name field will be saved in the
login column
- Click the user's name (login) on the top right
- Click logout
- Click Login on the top right
- Fill the user's credentials and login
You can check the user was created and saved to the Database with your custom scheme, custom timestamps
and custom hashing.
Hope it helps!