Question

I have a basic form set up to allow a user to change their email address, and I'm doing the following validation on it before I change the email:

// Set up the form validation
$validator = Validator::make(
    Input::all(),
    array(
        'email' => 'email|unique:users',
        'password' => 'required'
    )
);

// If validation fails, redirect to the settings page and send the errors
if ($validator->fails())
{
    return Redirect::route('settings')->withErrors($validator)->withInput();
}

This works fine, however after this basic validation I'd like to check if the user supplied a correct password. To do so, I'm doing the following with Laravel's basic authentication library:

// Find the user and validate their password
$user = Auth::user();

if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
{
    die("failed to authenticate");
}

Rather than handling the logic to tell the user their password is incorrect myself, I'd rather just add a form error to the password input so it shows up just like regular form validation. Something like so:

if (!Auth::validate(array('username' => $user->username, 'password' => Input::get('password'))))
{
    $validator->addError('password', 'That password is incorrect.');
    return Redirect::route('settings')->withErrors($validator)->withInput();
}

That way, the incorrect password error will show next to my password input and look like proper form validation.

How can I do this?

Was it helpful?

Solution

See Darren Craig's answer.

One way to implement it though.

// inside if(Auth::validate)
if(User::where('email', $email)->first())
{
    $validator->getMessageBag()->add('password', 'Password wrong');
}
else
{
    $validator->getMessageBag()->add('email', 'Email not found');
}

OTHER TIPS

There is one problem with the accepted answer (and Laravel's Validator in general, in my opinion) - the validation process itself and validation status detection is merged into one method.

If you blindly render all validation messages from the bag, it's no big deal. But if you have some additional logic that detects if the validator has failed or not and does additional actions (such as feeding international text messages for current validated form fields), then you have a problem.

Demonstration:

    // let's create an empty validator, assuming that we have no any errors yet
    $v = Validator::make([], []);

    // add an error
    $v->errors()->add('some_field', 'some_translated_error_key');
    $fails = $v->fails(); // false!!! why???
    $failedMessages = $v->failed(); // 0 failed messages!!! why???

Also,

    $v->getMessageBag()->add('some_field', 'some_translated_error_key');

yields the same results. Why? Because if you look into Laravel's Validator code, you will find the following:

public function fails()
{
    return ! $this->passes();
}

public function passes()
{
    $this->messages = new MessageBag;

As you can see, fails() method essentially clears away the bag losing all the messages you have appended, and thus making the validator assume that there are no errors.

There is no way to append errors to existing validator and make it fail. You can only create a new validator with custom errors like this:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'Required:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `required` rule

If you don't like the idea of abusing required validation rule for custom appended errors, you can always extend Laravel Validator with custom rules. I added a generic failkey rule and made it mandatory this way:

    // in custom Validator constructor: our enforced failure validator
    array_push($this->implicitRules, "Failkey");

    ...


/**
 * Allows to fail every passed field with custom key left as a message
 * which should later be picked up by controller
 * and resolved with correct message namespaces in validate or failValidation methods
 *
 * @param $attribute
 * @param $value
 * @param $parameters
 *
 * @return bool
 */
public function validateFailkey($attribute, $value, $parameters)
{
    return false; // always fails
}

protected function replaceFailkey($message, $attribute, $rule, $parameters)
{
    $errMsgKey = $parameters[0];

    // $parameters[0] is the message key of the failure
    if(array_key_exists($errMsgKey, $this->customMessages)){
        $msg = $this->customMessages[$parameters[0]];
    }       
    // fallback to default, if exists
    elseif(array_key_exists($errMsgKey, $this->fallbackMessages)){
        return $this->fallbackMessages[$parameters[0]];
    }
    else {
        $msg = $this->translator->trans("validation.{$errMsgKey}");
    }

    // do the replacement again, if possible
    $msg = str_replace(':attribute', "`" . $this->getAttribute($attribute) 
            . "`", $msg);

    return $msg;
}

And I can use it like this:

    $v = Validator::make(['some_field' => null],
            ['some_field' => 'failkey:some_translated_error_key']);
    $fails = $v->fails(); // true
    $failedMessages = $v->failed(); // has error for `Failkey` rule

Of course, that's still a hacky way to work around the issue.

Ideally, I would redesign the Validator to clearly separate its validation phase from status detection (separate methods for validate() and passes() or better isValid()) and also add convenience methods to manually fail specific field with specific rule. Although that also might be considered hacky, but still we have no other choice if we want to use Laravel validator not only with Laravel's own validation rules, but also our custom business logic rules.

Moreover, it could be helpful to add the following Redirect::back() function:

$validator->getMessageBag()->add('password', 'Password wrong');    
return Redirect::back()->withErrors($validator)->withInput();

According to

The Alpha

(http://heera.it/laravel-manually-invalidate-validation#.VVt7Wfl_NBc)

Alternate syntax:

$validator->errors()
          ->add('photos', 'At least one photo is required for a new listing.');

user Matt K said in a comment that laravel has since implemented validation hooks, which does exactly what we want:

$validator = Validator::make(...);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add('field', 'Something is wrong with this field!');
    }
});

if ($validator->fails()) {
    // this actually runs! even if the original validator succeeded!
}

If you are using ajax calls, don't forget to throw a ValidationException.

if ($subscribed) {
    $validator->errors()->add('email', __('Your email is already subscribed.'));
    throw new ValidationException($validator);
}
 $validator -> errors() -> add('attribute', 'value');
 return redirect($request -> url())
                    -> withErrors($validator)
                    -> withInput();

In "value" You can pass anything.

I understand why you want this, however it's actually bad practice from a security point of view to return a message that indicates whether the username and/or the password is incorrect. It would allow a hacker to understand whether they got the username or password correct.

It's better to return a generic message like 'Your credentials are incorrect', which you wouldn't want to be displayed next to your fields anyway.

I solved a similar problem with validation and custom validation. In my case, I need to verify that uploaded file with the form is a valid image and also the post data, so I need to run a validation test for the file and the validation tests for the post data. I was a problem when I tried to return back my custom validation data, only Laravel's validation errors was present. According to @JustAMartin post, I've been coded a solution that shows all errors.

    //Creem una instància del validador. Açò ens permet manipular-lo
    $validator = Validator::make($request->all(), [
        'nomCompanyia' => 'required',
        'urlCompanyia' => 'url'
    ]);

    $imageError = false;
    $imgOriginal = null;
    $imgMitjana = null;
    $imgXicoteta = null;
    $fallaValidacio = !$validator->passes(); //-> Retorna true si cap error, false en cas contrari.

    if($request->hasFile('logoCompanyia') && !$fallaValidacio)
    {
        $imatge = $request->file('logoCompanyia');

        if($imatge->isValid() && $this->verificaExtensionsImatges($imatge->getClientOriginalExtension(), $imatge->guessExtension()))
        {
            $sPath = $imatge->store('images/companyies/', 'public');
            $fullPathOriginal = public_path() . "/storage/" . $sPath;
            $fpInfo = pathinfo($fullPathOriginal);
            $imgOriginal = sprintf("%s.%s", $fpInfo['filename'], $fpInfo['extension']);

            //Crear les miniatures
            $mitjana = Image::make($fullPathOriginal)->widen(300, function ($constraint) {
                $constraint->upsize();
            });

            $imgMitjana = sprintf("%s_300.%s", $fpInfo['filename'], $fpInfo['extension']);
            $mitjana->save($fpInfo['dirname'] . '/' . $imgMitjana);

            $xicoteta = Image::make($fullPathOriginal)->widen(100, function ($constraint) {
                $constraint->upsize();
            });

            $imgXicoteta = sprintf("%s_100.%s", $fpInfo['filename'], $fpInfo['extension']);
            $xicoteta->save($fpInfo['dirname'] . '/' . $imgXicoteta);
        }
        else
        {
            $imageError = true;
            $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o està corrupte. Només s'accepten els formats: .jpg, .jpeg, .png, .gif");
        }
    }
    else
    {
        $imageError = true;
        $validator->getMessageBag()->add('logoCompanyia', "Sembla que el fitxer d'imatge no és vàlid o ha sigut rebutjat per el servidor si és massa gran.");
    }

    if($fallaValidacio || $imageError)
    {
        $data['mode'] = "nou";
        $data['urlFormulari'] = "administracio/companyies/afegir";
        $data['nomCompanyia'] = $request->nomCompanyia;
        $data['idCompanyia'] = 0;
        $data['urlCompanyia'] = $request->urlCompanyia;
        $data['logoCompanyia'] = $request->logoCompanyia;
        $data['errors'] = (object) $validator->errors();

        return view($this->formulariTemplate, $data);
    }

    $companyia = new Companyies();
    $companyia->nom = $request->nomCompanyia;
    $companyia->url = $request->urlCompanyia;
    $companyia->logo_original = $imgOriginal;
    $companyia->logo_300 = $imgMitjana;
    $companyia->logo_100 = $imgXicoteta;

    $companyia->save();

As you can see I only makes one call to $validator->passes() and I store the result in a variable. When I call this method, all Laravel tests are maked. If they're passed or not result is stored in the variable, so you can test your variable later. That permits make the tests on the file to finally determine if all data is OK or not.

If there are errors, I redirect back with the view() helper, adding all data: input and errors. If there are not errors the normal behaviour for the method is continued.

I have been searching for ages to accomplish this, I learned that what @JustAMartin said is true. It is not really possible with the Laravel standard validator, but to make a quick workaround for this, make your own boolean toggler through your script.

Initiate it with a default value of your liking and then check through your validation and then store your errors in the Laravel validator because that still works, so you can use it perfectly fine for error storing, and then in the end check your boolean (true|false) in the end and then either success or throw all the errors as normally, here comes an example of how I did.

My case is that I want to check a text field if they wrote exactly "Confirm deletion" and then afterwards I want to check if their password provided is correct in order to delete their account, here you go:

My boolean I use as workaround is $eventSuccess

public function accountDeletion(Request $request)
{
    $eventSuccess = false;
    $validator = Validator::make($request->all(), [
        'textConfirm' => 'string|in:Confirm deletion'
    ]);
    if (!$validator->fails()) {
        $eventSuccess = true;
    }

    $user = Auth::user();
    if (Auth::validate(array('username' => $user->username, 'password' => $request->get('password')))) {
        $eventSuccess = false;
        $validator->messages()->add('password', 'Wrong password.');
    }

    if ($eventSuccess) {
        $user->delete();
        return true;
    }
    return $validator->messages()->get('*');
}

This does the trick for me, hope someone can use it.

Best regards,
Casper Thomsen

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top