Using ValidatesRequests trait in Laravel - php

Here's the code from my AuthController:
public function postRegister(Request $request)
{
$this->validate($request, [
'name' => 'required|min:3',
'email' => 'required|email|unique:users',
'password' => 'required|min:5|max:15',
]);
}
If the validation fails I'm getting redirected to the previous page. Is there a way to pass additional data along with the input and the errors (which are handled by the trait)?
Edit: Actually, the trait does exactly what I want, except the additional data I want to pass. As #CDF suggested in the answers I should modify the buildFailedValidationResponse method which is protected.
Should I create a new custom trait, which will have the same functionality as the ValidatesRequests trait (that comes with Laravel) and edit the buildFailedValidationResponse method to accept one more argument or traits can be easily modified following another approach (if any exists)?

Sure you can, check the example in the documentation:
http://laravel.com/docs/5.1/validation#other-validation-approaches1
Using the fails(); method, you can flash the errors and inputs values in the session and get them back with after redirect. To pass other datas just flash them with the with(); method.
if ($validator->fails()) {
return back()->withErrors($validator)
->withInput()
->with($foo);
}

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 api route validation not validating

I did move from Lumen to Laravel and now converting my project over. Everything is working except the validation. For some reason, if I try to validate, it just redirects to the welcome.blade.php view. What could cause this?
I am using only the API part of routes, not the view. I am not dealing with views. I am using the stateless part of Laravel.
According to documentation, I can validate like this:
$this->validate($request, [
'title' => 'required|unique|max:255',
'body' => 'required',
]);
If validation passes, your code will keep executing normally. However,
if validation fails, an
Illuminate\Contracts\Validation\ValidationException will be thrown.
I also tried to force it to return JSON response without success:
$validator = $this->validate($request, ['email' => 'required']);
if ($validator->fails()) {
$messages = $validator->errors();
return new JsonResponse(['status' => 'error', 'messages' => $messages]);
}
However, mine doesn't fail but just returns the welcome view with response code of 200. I have tried pretty much all the possible validation methods from the documentation and from google. Non of them are working.
I even tried with clean laravel install, declared one test route and test controller which had the validation and the result is the exact same.
Is the validation even meant to be compatible with the restful/stateless part of Laravel?
Any suggestion is much appreciated.
1- first the unique key needs a table, per example if you want the email to be unique in the users table you do as follows:
'email' => 'required|unique:users',
I think may be you have placed your route in route/web.php file. Replace that code from web.php to api.php
Try to place your API endpoints in route/api.php file.
And remember you need to add prefix /api in your route.
Ex : test.com/api/users.

redirect route for validation in laravel

I have a problem with the redirectRoute property in Laravel. What I want to do is: after the validation of the request, for example, the register request fails, redirect to another view with the proper errors. I have tried with protected $redirectRoute = '/route-name' in the RegisterController but it's not working. I'm using Laravel 5.4
See here: https://laravel.com/docs/5.4/validation#manually-creating-validators
You'll need to manually create the validator and then you can tell it exactly where to redirect to if the validation fails:
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
And if you are using Form Requests, you can override the following properties to redirect to a custom location:
protected $redirect; // A simple URL. ex: google.com
protected $redirectRoute; // A route name to redirect to.
protected $redirectAction; // A controller action to redirect to.
Updated answer, tested in Laravel 8. it is possible to create a ValidationException and set a custom redirect on it.
Summary:
use Illuminate\Validation\ValidationException;
$validator->setException( (new ValidationException($validator))->redirectTo('your/custom/location'));
$validator->validate();

PHPUnit Laravel Hash not available

I have a Unit test in Laravel for testing an API call that looks like this, however I am getting the following runtime error when running it:
RuntimeException: A facade root has not been set.
I'm creating a user in the setup method, with the intent to delete it again in the tearDown() method, then run my auth test.
Firstly, is there a better way of doing what I want? For example Mocking a user without touching the database? And secondly, how do I set a 'facade root' or what does that error mean exactly? I've tried not bothering to hash that particular field for the purposes of creating a Dummy user, but the error then seems to move to the model, where (again) the Hash facade class is used.
Is there any additional steps to setup the environment so these facades can be used in testing?
Thanks in advance.
use Illuminate\Support\Facades\Hash;
/*
* Make sure the structure of the API call is sound.
*/
public function testAuthenticateFailed()
{
$this->json('POST', $this->endpoint,
[ 'email' => 'test#test.com',
'password' => 'password',
])
->seeJsonStructure([
'token'
]);
}
//create a user if they don't already exist.
public function setup()
{
$user = User::create([
'company_id' => 9999,
'name'=>'testUser',
'email' => 'test#test.com',
'password' => 'password',
'hashed_email' => Hash:make('test#test.com'),
]);
}
Try to use this instead:
\Hash::make('test#test.com'),
It's a good idea to use bcrypt() global helper instead of Hash::make()
Also, add this to setUp() method:
parent::setUp();
You could use the DatabaseMigrations or DatabaseTransactions trait that comes with Laravel so you don't have to delete the User manually.
You could add a Mutator to your User class, which will automatically hash the password when a User is created.
// https://laravel.com/docs/5.3/eloquent-mutators
public function setPasswordAttribute($value) {
$this->attributes['password'] = bcrypt($value);
}

custom validation in laravel

I am integrating Amazon MWS in Laravel, so far so good, Now, in my dashboard, I have created a form where user can put his Seller ID and Auth Token ( provided by Amazon). My code looks like this
$store = StoreController::Find($id)->first();
$this->validate($request, [
'name' => 'required|max:255',
'merchantId' => 'required|max:255',
'authToken' => 'required|max:255',
'marketplaceId' => 'required|max:255',
]);
$mws = new mwsController();
$result = $mws->checkCredentials($store);
if ($result) {
//credentials OK, Force Fill in database
//OK with it
// ALSO, I want to disable future Form Edits, any idea?
}else{
//return error on form, saying Merchant ID and Auth Token pair is invalid
//stuck at this point
//documentation doesnt help
}
1: Problem 1:
How can I return custom error as I commented in Code
2: I want to disable future edits in Form If Credentials Ok
Explanation
Once I have validated credentials, and updated it Database, I want that user can see the form, but he can not edit Auth Token , Merchant ID or any other field in the form.
Any guide line and help is highly appreciated
thanks
I would keep your validation in the validate method. That way your error response will work out of the box. How to extend the validator is explained here: https://laravel.com/docs/5.0/validation#custom-validation-rules
It could look something like this:
Validator::extend('mwsToken', function($attribute, $value, $parameters)
{
// check if the token is valid an return result
});
And then you can just use it in your controller:
$this->validate($request, [
'name' => 'required|max:255',
'merchantId' => 'required|max:255',
'authToken' => 'required|max:255|mwsToken',
'marketplaceId' => 'required|max:255',
]);
No need for the if/else anymore. You can just assume the token is valid there, since validation already passed. And error reporting will work automatically if you set up your Validator correctly.
As for the second question, not really sure what you mean. If you do not want to allow edits in certain cases, just don't render the form. Something like this perhaps (in your controller):
public function getEdit($id) {
$model = Model::findOrFail($id);
if ($model ->hasPropertyThatMeansNoEdit()) {
abort(403);
}
// build and render edit form
}
Don't forget to do something similar in your post handler. Always assume the user is malicious. It isn't because there is no form, that a POST request can't be made, ie. by manipulating the request of a different model.
One last side note on your architecture. I noticed in your snippet you are calling your controllers directly (StoreController, mwsController). I don't think you should be doing that. Controllers are there to handle your requests, and nothing else. If you have reusable blocks of code in them, consider moving that code to a Service or a Command, and calling that command from inside your controller. It will make your controllers a lot cleaner (SRP) and makes it easier to reuse those Commands later in ie. an API or a Queue Job or something like that.
The answer to your first question is pointed out in the docs: simply add a message bag to the response. Also check out the beginner tutorial video's (by Jeffrey Wade) on Laracast, they are really helpful. The code would be:
public function store(Request $request, $id)
{
// ...
$this->validate($request, [
'name' => 'required|string|max:255',
'merchantId' => 'required|integer|max:255',
'authToken' => 'required|string|max:255',
'marketplaceId' => 'required|integer|max:255',
]);
$mws = new mwsController();
if ($mws->checkCredentials($store)) {
// Your code here
return redirect('home')->with(['success' => 'Everything OK']); // Flash session
}
return redirect('home')->withErrors(['Merchant ID and Auth Token pair is invalid']); // Error bag
}
And to display:
#if (session('success'))
<div class="positive message">{{ session('success') }}</div>
#endif
#if (count($errors) > 0)
<div class="negative message">{{ $errors->first() }}</div>
#endif
You're second question is pretty hard to answer since you've given no code or example to work with. Maybe I'm not understanding the question, but I think you are looking for middleware.
Edit: To answer the second question, add a column named 'validated' (default 0) in your database table. If the credentials are OK, update that column and set it to 1. Use that variable in your template to manipulate the form fields, for instance:
<input type="text" name="merchantId" {{ $validated ? '' : 'disabled' }}/>

Categories