How to change 'users' auth table and use another instead Laravel - php

So I just started a Laravel Project with Breeze, and I wanted to change the default table users , the problem is it didn't work, I did my research for days and I didn't get any successful result
I will try to explain what is the problem and what have I tried so far.
First, I created a new table called users_data, and this table, is completely different than the users table.
The fields that users_data has, are for example: name_value, password_value, age_value, email_value, etc. (I have to mention too that for the table users_data, it doesn't use a migration, because I already have an sql file, and added it directly to the db (I already have tables created, with primary keys, and foreign key, so i couldn't do the migration because it would take me a lot of time), and without the migration I can still get the data, so I don't think it could be this the problem).
Actually I'am using Breeze, however, I used Auth scaffolding (PHP artisan make: Auth) too
What have I tried:
After several days of search, first I have created a new Model, called UsersModel, the content of this is the same as User Model however what I change is:
protected $table = 'users_data';
protected $fillable = [
*name_value*,
*password_value*,
];
and an extra function to override the default password of breeze or Auth (I guess):
public function getAuthPassword()
{
return $this->password_value;
}
next I went to conf/auth.php
there I specified the Model:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\UsersModel::class,
],
and the table to use:
/*
|--------------------------------------------------------------------------
| Authentication Table
|--------------------------------------------------------------------------
|
| When using the "Database" authentication driver, we need to know which
| table should be used to retrieve your users. We have chosen a basic
| default value but you may easily change it to any table you like.
|
*/
'table' => 'users_data',
after this I went to the view login.blade.php, and changed only the email input (for what I read on different pages, changing the password input will cause different problems, because I would need to make a lot of changes to make it work so the best idea is to override it with getAuthPassword, specified in the model):
new name input:
x-input id="email" class="block mt-1 w-full" type="text" name="name_value" :value="old('name_value')" required autofocus />
After all this I went to LoginRequest (the validation for the login), where I replaced email for name_value
I tried to debug this:
dd(Auth::attempt($this->only('name_value', 'password'), $this->boolean('remember')));
and returns false
I noticed that there's a function in vendor/laravel/ui/auth-back/AuthenticatesUsers
called username(), that returns 'email'
when I saw that I remembered a page that said that this function could override too, so I changed the return value to name_value, and it doesn't do nothing
last, just to clarify,
I don't need the Register site I only need the login page, so for that in the $fillable I didn't add all the columns of the database, just the ones that I need to log in (name_value, password_value)
If anyone could help me and guide me it will be great, because I'am running out of ideas (I could do it with PHP alone, however, I need the ->middleware ['Auth], is there a way to activate the middlware if the user exists?)

So you might have a model named users_data.php. Go inside it and change the code to something like that.
STEP:1
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\users_data as Authenticatable; //Add this line for Auth.
class users_data extends Authenticatable //Extends child from Authenticatable parent class obj.
{
use HasFactory;
protected $fillable = ['column1', 'column2', 'column3', .....];
}
STEP:2
Go to config/auth.php. You may found something like this below.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
Change the 'model' => App\Models\User::class, to 'model' => App\Models\users_data::class,
This is the main character in this drama that putting your application to users table by default.
STEP:3
Comment off the User.php so that no future conflict create.
Now your application has been diverted to your desired table and ready to login.

I don't think it's good practice to be editing vendor files. We don't push them to version control so other developers won't have your changes. Also, Laravel already has a way to override the username value without editing the vendor files.
Just use the trait in your auth controller like this:
public class MyLoginController {
use AuthenticatesUsers; // or you can also use ThrottlesLogins trait
// then override the username function here
public function username() {
return 'name_value';
}
}
To override the password you can define this on your User model:
public function getPasswordAttribute() {
return $this->attributes['password_value'];
}
public function getAuthPassword() {
return $this->password_value;
}
I haven't tested this but based on the docs this is how you should do it. Also make sure to read this Laravel doc.

Related

How to allow to use the master password in Laravel 8 by overriding Auth structure?

I've got a website written in pure PHP and now I'm learning Laravel, so I'm remaking this website again to learn the framework. I have used built-in Auth Fasade to make authentication. I would like to understand, what's going on inside, so I decided to learn more by customization. Now I try to make a master password, which would allow direct access to every single account (as it was done in the past).
Unfortunately, I can't find any help, how to do that. When I was looking for similar issues I found only workaround solutions like login by admin and then switching to another account or solution for an older version of Laravel etc.
I started studying the Auth structure by myself, but I lost and I can't even find a place where the password is checked. I also found the very expanded solution on GitHub, so I tried following it step by step, but I failed to make my own, shorter implementation of this. In my old website I needed only one row of code for making a master password, but in Laravel is a huge mountain of code with no change for me to climb on it.
As far I was trying for example changing all places with hasher->check part like here:
protected function validateCurrentPassword($attribute, $value, $parameters)
{
$auth = $this->container->make('auth');
$hasher = $this->container->make('hash');
$guard = $auth->guard(Arr::first($parameters));
if ($guard->guest()) {
return false;
}
return $hasher->check($value, $guard->user()->getAuthPassword());
}
for
return ($hasher->check($value, $guard->user()->getAuthPassword()) || $hasher->check($value, 'myHashedMasterPasswordString'));
in ValidatesAttributes, DatabaseUserProvider, EloquentUserProvider and DatabaseTokenRepository. But it didn't work. I was following also all instances of the getAuthPassword() code looking for more clues.
My other solution was to place somewhere a code like this:
if(Hash::check('myHashedMasterPasswordString',$given_password))
Auth::login($user);
But I can't find a good place for that in middlewares, providers, or controllers.
I already learned some Auth features, for example, I succeed in changing email authentication for using user login, but I can't figure out, how the passwords are working here. Could you help me with the part that I'm missing? I would appreciate it if someone could explain to me which parts of code should I change and why (if it's not so obvious).
I would like to follow code execution line by line, file by file, so maybe I would find a solution by myself, but I feel like I'm jumping everywhere without any idea, how this all is connected with each other.
First of all, before answering the question, I must say that I read the comments following your question and I got surprised that the test you made returning true in validateCredentials() method in EloquentUserProvider and DatabaseUserProvider classes had failed.
I tried it and it worked as expected (at least in Laravel 8). You just need a an existing user (email) and you will pass the login with any non-empty password you submit.
Which of both classes are you really using (because you don't need to edit both)? It depends of the driver configuration in your auth.php configuration file.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
As you already thought, you can simply add an "or" to the validation in the validateCredentials() method, comparing the $credentials['password'] to your custom master password.
Having said that, and confirming that's the place where you'd have to add your master password validation, I think the best (at least my recommended) way to accomplish your goal is that you track the classes/methods, starting from the official documentation, which recommends you to execute the login through the Auth facade:
use Illuminate\Support\Facades\Auth;
class YourController extends Controller
{
public function authenticate(Request $request)
{
//
if (Auth::attempt($credentials)) {
//
}
//
}
}
You would start by creating your own controller (or modifying an existing one), and creating your own Auth class, extending from the facade (which uses the __callStatic method to handle calls dynamically):
use YourNamespace\YourAuth;
class YourController extends Controller
{
//
public function authenticate(Request $request)
{
//
if (YourAuth::attempt($credentials)) {
//
}
//
}
}
//
* #method static \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard guard(string|null $name = null)
//
class YourAuth extends Illuminate\Support\Facades\Facade
{
//
}
And use the same logic, overriding all the related methods in the stack trace until you get to use the validateCredentials() method, which in the end will also be overrided in your own CustomEloquentUserProvider class which will be extending fron the original EloquentUserProvider.
This way, you will have accomplished your goal, and kept a correct override of the whole process, being able to update your laravel installation without the risk of loosing your work. Worst case scenario? You'll have to fix any of your overriding methods in case that any of them has drastically changed in the original classes (which has a ver low chance to happen).
Tips
When making the full overriding, maybe you'll prefer to add some significant changes, like evading the interfaces and going straight for the classes and methods you really need. For example: Illuminate/Auth/SessionGuard::validate.
You would also wish to save your master password in an environment variable in your .env file. For example:
// .env
MASTER_PASSWORD=abcdefgh
and then call it with the env() helper:
if ($credentials['password'] === env('MASTER_PASSWORD')) {
//
}
Nice journey!
A more complete solution would be the define a custom guard and use that instead of trying to create your own custom auth mechanism.
Firstly, define a new guard within config/auth.php:
'guards' => [
'master' => [
'driver' => 'session',
'provider' => 'users',
]
],
Note: It uses the exact same setup as the default web guard.
Secondly, create a new guard located at App\Guards\MasterPasswordGuard:
<?php
namespace App\Guards;
use Illuminate\Auth\SessionGuard;
use Illuminate\Support\Facades\Auth;
class MasterPasswordGuard extends SessionGuard
{
public function attempt(array $credentials = [], $remember = false): bool
{
if ($credentials['password'] === 'master pass') {
return true;
} else {
return Auth::guard('web')->attempt($credentials, $remember);
}
}
}
Note:
You can replace 'master pass' with an env/config variable or simply hardcode it. In this case I'm only checking for a specific password. You might want to pair that with an email check too
If the master pass isn't matched it falls back to the default guard which checks the db
Thirdly, register this new guard in the boot method of AuthServiceProvider:
Auth::extend('master', function ($app, $name, array $config) {
return new MasterPasswordGuard(
$name,
Auth::createUserProvider($config['provider']),
$app->make('session.store'),
$app->request
);
});
Fourthly, in your controller or wherever you wish to verify the credentials, use:
Auth::guard('master')->attempt([
'email' => 'email',
'password' => 'pass'
]);
Example
Register the route:
Route::get('test', [LoginController::class, 'login']);
Create your controller:
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
class LoginController
{
public function login()
{
dd(
Auth::guard('master')->attempt([
'email' => 'demo#demo.com',
'password' => 'master pass'
]),
Auth::guard('master')->attempt([
'email' => 'demo#demo.com',
'password' => 'non master'
]),
);
}
}
and if you hit this endpoint, you'll see:
Where true is where the master password was used and false is where it tried searching for a user.
Final Thoughts
From a security standpoint you're opening yourself up to another attack vector and one which is extremely detrimental to the security of your system and the privacy of your users' data. It would be wise to reconsider.
This validation of credentials should ideally be separated from your controller and moved to a Request class. It'll help keep your codebase more clean and maintainable.
Instead of trying to roll your own, you could as well as use a library, which does just that:laravel-impersonate (it's better tested already). This also comes with Blade directives; just make sure to configure it properly, because by default anybody can impersonate anybody else.
There even is (or was) rudimentary support available with: Auth::loginAsId().
Here is a possible solution.
To use a master password, you can use the loginUsingId function
Search the user by username, then check if the password matches the master password, and if so, log in with the user ID that it found
public function loginUser($parameters)
{
$myMasterHashPassword = "abcde";
$username = $parameters->username;
$password = $parameters->password;
$user = User::where('username', $username)->first();
if (!$user) {
return response("Username not found", 404);
}
if (Hash::check($myMasterHashPassword, $password)) {
Auth::loginUsingId($user->id);
}
}

Laravel / Lumen ReadOnly Model?

There are some tables in our system which are being fed using 3rd party APIs and our system is supposed only read data from them and never Insert or Update anything.
Is there any feature in Laravel/Lumen, where we can mention in the Model to disallow/disable insert/update queries like we have the option public $timestamps = false; to disable the timestamps?
The tables are in same database else we would have restricted based on MySQL user.
There are a few ways.
OPTION 1: Probably the quickest is this "read-only" model trait. https://github.com/michaelachrisco/ReadOnlyTraitLaravel
It protects you from...
create
forceCreate
save
update
firstOrCreate
firstOrNew
delete
destroy
restore
forceDelete
performDeleteOnModel
push
finishSave
performUpdate
touch
insert
truncate
OPTION 2: A completely different way to do it is on the db config and model connection. So, this has 2 parts.
project/config/database.php
Duplicate and tweak the db connection.
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
...
'readonly' => [
'driver' => 'mysql',
'read' => [
'host' => env('DB_HOST', '127.0.0.1')
],
'write' => [
'host' => 'this.will.404'
],
...
project/app/MyReadOnlyModel.php
class MyReadOnlyModel extends Model
{
protected $connection = 'readonly';
...
}
If you are caught in the trap of wanting the Model to be writable sometimes... I would suggest having 2 models on the same table.
app/Normal/MyModel.php
app/ReadOnly/MyModel.php
yes,
as a secure way:
as you can restricting some actions on Database.
ReadOnly Model
but you can disable the eloquent models too.
laravel models are extended from Illuminate\Database\Eloquent\Model
you can extend an ReadOnlyModel from Model.
then extend any model you want from that class.
this class should Override any method which writes data in db,
so i follow the source code:
Update and updateOrFail, push and etc was using used Model->save() method.
While create,creteOrFail , delete and etc were places in Builder which uses Model->save() method
The save() method used performUpdate or performInsert with someevent triggering ...
so the simplest thing you can do to stop model from touching databases is to implement:
<?php
namespace App\ReadOnlyDB;
use Illuminate\Database\Eloquent\Model;
/**
* Just Extend all of Your Models from This Class
*
*/
class ReadOnlyModel extends Model
{
public function save(){
// do nothing
}
}
save() methods in Relation and BelongsTo classes would use Model->save() method too.
The most secure way will be to create a second MySQL user with the readonly only on the tables.
Then in Laravel you can create a specific connection with the restricted MySQL user in your config/database.php.
Then in the Model specify the wanted connection through the protected $connection property.
I think the only proper way to manage this is to have access restrictions at the MySQL user side. Here's why:
If you are stubbing (disabling) save/update methods from the Eloquent class, you only disable save/updates for your own code. This is analogous to locking a door while hanging the key on the door handle. Nothing prevents you from doing the saves/updates since you are not bound to use the Eloquent class.
In addition, 3rd party packages may persist data which does not depend on your code. You basically have to check every single line of code of theirs to make sure your database is not updated.
Therefore, apply these restrictions at the database user level. Then, when for some reason your (or anyone elses) code calls $myModel->save(), you should face an exception from the ORM that states you do not have permission. Then, handle it accordingly (like returning a particular HTTP status code).
Create an additional mySql user with read-only privileges.
in .env file add the following
DB_CONNECTION_SECOND=mysql
DB_HOST_SECOND=127.0.0.1
DB_PORT_SECOND=3306
DB_DATABASE_SECOND=database
DB_USERNAME_SECOND=user_2
DB_PASSWORD_SECOND=secret
in config/database.php add the following
'mysql2' => [
'driver' => env('DB_CONNECTION_SECOND'),
'host' => env('DB_HOST_SECOND'),
'port' => env('DB_PORT_SECOND'),
'database' => env('DB_DATABASE_SECOND'),
'username' => env('DB_USERNAME_SECOND'),
'password' => env('DB_PASSWORD_SECOND'),],
in your controller specify the connection name..
DB::connection('mysql2')->select(...);
Maybe using an empty fillable attribute in your model resolve your problem!
protected $fillable = [];
Set a model accessor to throw an exception when touching an attribute.
But this is more of a read-only attribute instead of a read-only model since it requires one accessor per attribute.
use Exception;
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function value(): Attribute
{
return Attribute::make(
set: fn () => throw new Exception('Model is readonly'),
);
}

Laravel use custom USER-Model in package

I´ve developed a package stored in LaravelRoot/packages/myname/project/ in Laravel.
Inside my package i´ll have to use an extended user-model containing a relationship not mentioned in the default usermodel.
Now here is my question:
How can i override the default User-Model inside my package?
More details:
If i receive the current user via Auth::user() inside my package i´ll receive an object using the default App\User.
I have extended this model containing now a new relationfunction, stored in LaravelRoot/packages/myname/project/src/App/Models/User (namespace: Myname\Myproject\App\Models).
Here´s my relation-function:
/**
* The roles that belong to the user.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class)->withTimestamps();
}
This relation is only used in my package, so i don´t want to store this function inside the default App\User. Is it possible to receive an object of my extended usermodel using Auth::user() in my package or do i have to receive this manually each time i need this one?
I think, the easiest way is to make your package model via extending of App\User model. You need just to open config/auth.php and to setup your model class.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => Project\Models\User::class,
],
You will get correct model via Auth::user().

Integrate authentication to existing Laravel project and users database

I am a totally newbie to Laravel, but after following lots and lots of guide, my project was really going well.
Now, I have a user table named UsersTable, which is going to store both Username and Password.
However, after trying php artisan make:auth and a lot of Googling, I found no clear, exact way of defining both the User table name, and the Username-Password field used for authentication.
I've tried the original Email-Password authentication on the table named users on another project I created for trying this, and it works like a charm. I believe that as Laravel already provide the authentication system, why reinvent the wheels when (I believe) there's a way to customise it.
Thanks in advance! :D
Use php artisan make:auth and then :
To change the default table from users which uses the User model
you have to navigate to : config/auth.php
And make these changes
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\UsersTable::class, //put the right model name here or you can use database driver
],
],
To change the table for an eloquent model you can do this
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UsersTable extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'UsersTable';
}
To change the default login credential from email to user name , just add this to the Authcontroller generated by laravel
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
protected $redirectTo = '/home';
protected $username = 'username'; // you can put whatever column you want here from your table
Now
edit laravel generated Views and change the email input name to name="username" then extend the main.blade.php, do some styling and you are good to go i guess
EDIT : Dont forget to change the validation stuff
Laravel basic authentication system is not the fastest way to create your own auth method ... I suggest to you Sentinel Package to customize your authentications ...
for example to authenticate your user by using user name you can do like this :
Sentinel::authenticate(array(
'username' => 'john.doe',
'password' => 'foobar',
));
for me it's the fastest way to customize your authentication method i hope that will help you.

Doubts about Yii2 RBAC

I've been developing web apps using Yii 1.1.14 so far, but now it's time for an upgrade.
The company where I work has developed its own Access Control system, and I was really OK with it until I saw what it was really like... A combination of 8 tables in the database (not counting the users table), with a bunch of foreign keys.
1 table for controllers
1 table for the actions
1 table for the menu categories
1 table for types of users
And the other tables basically just connect 2 or 3 of those tables at a time.
It works well, but in my point of view it's highly time consuming to maintain all those tables, and at some point, when your application goes online, if it hits a certain amount of users it could get really slow. specially because 2 of those tables have the user's table primary key as foreign key.
So I've decided that, when I start developing on Yii 2, I'm going to start using RBAC, so I started looking for tutorials online... Only finding many different versions of the same code with author's role, and permissions for create or update posts.
I found a combination of 5 videos on Youtube, but they are about Yii 1 RBAC. They were helpful because I managed to understand most of RBAC's functionality, but I still have some doubts that I'll
enumerate below. And keep in mind that for this Access Control system I'm using the DBManager class.
My Doubts
Yii 1's RBAC used to have 3 tables: auth_assignment, auth_item and auth_item_child. Now in Yii 2 RBAC, a new table appears that is called auth_rule and I still don't understand what that specific table is doing there, how to use it or how to populate it.
I see that it's possible to restrict the user's access to some actions by using the controller's behavior method, and assigning access to some actions depending on the user's role, but when it comes to this I have to split my question into 2:
2.1. First: If you can just restrict the access to actions by setting it up in the behaviors method, then what's the use of saving permissions to the auth_item table?
2.2. Second: If you DO decide to control access according to permissions, then how exactly do you do it, because I find myself writing the following type of code inside of every function and I don't think using RBAC is supposed to be this tedious. There has to be another way.
public function actionView($id)
{
if(Yii::$app->user->can('view-users')){
return $this->render('view', [
'model' => $this->findModel($id),
]);
}else{
#Redirect to a custom made action that will show a view
#with a custom error message
$this->redirect(['//site/notauthorized']);
}
}
Because of the Access Control System that we use right now, when a user logs in, a complex query is executed that will end up returning an array that will be saved as a session variable, and will be used to create a menu with as many dropdownlists as menu categories, that the controllers that the user has access to belong to. How can this be done with RBAC?
I can only really answer 2.2 of your question, as 3 doesn't sound at all like something an RBAC should do. You could, however, get the information you needed from the rules table most likely, provided you followed a naming convention that matched your controllers or actions.
On to answering 2.2 though:
You can simply set the behavior like such:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'actions' => ['view'],
'roles' => ['view-users'], //<-- Note, rule instead of role
],
]
]
}
This doesn't solve a different problem of 'view-own-users' style permissions, as this needs to inspect the ActiveRecord model (well, at least it does in my application). If You want to achieve this, take a look at my post in the Yii forums here:
http://www.yiiframework.com/forum/index.php/topic/60439-yii2-rbac-permissions-in-controller-behaviors/#entry269913
I use it in one of the simplest method,I use them in the behaviours of my controller.
public function behaviors()
{
return [
'access' => [
'class' => \yii\filters\AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['sysadmin'],
'actions' => ['index','view','update'],
],
[
'allow' => true,
'roles' => ['staff'],
'actions' => ['index','create','update','view'],
],
],
],
];
}
Here roles are the one created in the auth-item table in the database and they have been assigned for users in auth-assignment table. In the behaviours we just use it as above. In the above code sysadmin can have access to index, view and update action, whereas staff can have access to index,create, update and view action.
Yii2 needs a little setup when it comes to using RBAC under your controllers AccessControl. I got around it by making my own AccessRule file.
namespace app\components;
use Yii;
class AccessRule extends \yii\filters\AccessRule
{
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if(Yii::$app->authManager->checkAccess($user->identity->code, $role))
return true;
}
return false;
}
then in your controller u can use something like this:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'ruleConfig' => [
'class' => 'app\components\AccessRule'
],
'rules' => [
[
'actions' => ['index', 'resource-type'],
'allow'=> true,
'roles' => ['admin'],
],
],
],
];
}
Where admin is defined as a auth_item and the user is in the auth_item_assignments.
As I have created a new Rbac system for yii2. you can direct permission for a action and action will show you are not authorisez for this action.
By this you find that you will only provide access for action that need to identify.
I uploaded my detail here you can find lot of solution here.
This is the best solution i could come up with when facing the need to filter access by permissions, it's bothersome but can be useful if you're trying to create roles in a productive enviroment and want to use rbac.
use yii\web\ForbiddenHttpException;
if(Yii::$app->user->can('view-users')){
return $this->render('view', [
'model' => $this->findModel($id),
]);
}else{
throw new ForbiddenHttpException('You dont have access to this site');
}

Categories