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.
Look at the config/auth. php to see how they are defined. Then you can set your custom guard as default and the normal auth flow will take over. If I implement custom Guard, what happens to all the nice things like throttling and error messages that laravels default guard implements.
Auth::routes() is just a helper class that helps you generate all the routes required for user authentication. You can browse the code here https://github.com/laravel/framework/blob/5.3/src/Illuminate/Routing/Router.php instead.
Just run php artisan make:auth and php artisan migrate in a fresh Laravel application. Then, navigate your browser to http://your-app.test/register or any other URL that is assigned to your application. These two commands will take care of scaffolding your entire authentication system!
In the user login controller in the login module, there are the following codes: $loggedIn = $this->auth->login( [ 'email' => $request->email, 'password' => $request->password, ], (bool) $request->get('remember_me', false) );
These packages are Laravel Breeze, Laravel Jetstream, and Laravel Fortify. Laravel Breeze is a simple, minimal implementation of all of Laravel's authentication features, including login, registration, password reset, email verification, and password confirmation.
If the password is valid, we need to inform Laravel's session that the user has confirmed their password. The passwordConfirmed method will set a timestamp in the user's session that Laravel can use to determine when the user last confirmed their password.
The auth.basic middleware is included with the Laravel framework, so you do not need to define it: // Only authenticated users may access this route... Once the middleware has been attached to the route, you will automatically be prompted for credentials when accessing the route in your browser.
By default, the auth.basic middleware will assume the email column on your users database table is the user's "username". If you are using PHP FastCGI and Apache to serve your Laravel application, HTTP Basic authentication may not work correctly.
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:
'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 tooThirdly, 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' => '[email protected]',
'password' => 'master pass'
]),
Auth::guard('master')->attempt([
'email' => '[email protected]',
'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
Request
class. It'll help keep your codebase more clean and maintainable.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With