Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

richardev's avatar

Password reset not returning error messages on validation

After reseting password, user gets e-mail with password reset link & token. By opening this link user is presented with Password Reset form (view). Until this point all works great.. but whenever I pass wrong e-mail to trigger "email not valid" error warning, it gives me nothing.

reset form

Inspecting C:\Users\richa\Projects\globeguru\cms\vendor\laravel\framework\src\Illuminate\Foundation\Auth\ResetsPasswords.php I came across responsibe function on line 3:

public function reset(Request $request)
    {
        $request->validate($this->rules(), $this->validationErrorMessages());

        // Here we will attempt to reset the user's password. If it is successful we
        // will update the password on an actual user model and persist it to the
        // database. Otherwise we will parse the error and return the response.
        $response = $this->broker()->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );

        // If the password was successfully reset, we will redirect the user back to
        // the application's home authenticated view. If there is an error we can
        // redirect them back to where they came from with their error message.
        return $response == Password::PASSWORD_RESET
                    ? $this->sendResetResponse($request, $response)
                    : $this->sendResetFailedResponse($request, $response);
    }

This line:

 $request->validate($this->rules(), $this->validationErrorMessages());

Which brings me to this function:

protected function validationErrorMessages()
    {
        return [];
    }

My question is - how can I trigger the error message and return it via validationErrorMessages() function?

UPDATE

Here is the reset form HTML

@section('reset-form')
    <div class="alert-container" >
        @if (session('status'))
            <div class="alert alert-success alert-dismissible fade show" role="alert">
                <p><strong>Success!</strong></p>
                <p>{{ session('alert') }}</p>
                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
        @endif

        @error('email')
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <p><strong>Error!</strong></p>
            <p>{{ $message }}</p>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        @enderror

        @error('password')
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <p><strong>Error!</strong></p>
            <p>{{ $message }}</p>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        @enderror

        @error('password_confirmation')
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
            <p><strong>Error!</strong></p>
            <p>{{ $message }}</p>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        @enderror
    </div>

    <div class="gg-login-wrap">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">{{ __('auth.reset_title') }}</h5>
                </div>
                <div class="modal-body">
                    <form class="gg-login" method="POST" action="{{ route('password.update') }}">
                        @csrf

                        <div class="form-group edit-input-wrap">
                            <input id="email" type="email" class="form-control edit-input @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
                            <label for="email">{{ __('auth.email') }}</label>

                            @error('email')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                            @enderror
                        </div>
                        <div class="form-group edit-input-wrap">
                            <input id="password" type="password" class="form-control edit-input @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
                            <label for="password">{{ __('auth.password') }}</label>

                            @error('password')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                            @enderror
                        </div>
                        <div class="form-group edit-input-wrap">
                            <input id="password_confirmation" type="password" class="form-control" name="password_confirmation" required autocomplete="password_confirmation">
                            <label for="password_confirmation">{{ __('auth.password_repeat') }}</label>

                            @error('password_confirmation')
                            <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                            @enderror
                        </div>
                        <input type="submit" class="btn btn-primary" value="{{ __('auth.reset') }}">
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection
0 likes
14 replies
bobbybouwmann's avatar

It doesn't seem that the problem is in the validationErrorMessages method. This method is there if you want to override the messages for the keys. However if you leave it empty the default messages will be used.

The question is here more about how you show your errors instead of if it's returned or not. It seems that something is done with the validation because the email field has a red border.

You can find here more about displaying error messages: https://laravel.com/docs/5.8/validation#working-with-error-messages

So can always do {{ dump($errors) }} in your view to see if any validation message was returned after submitting!

1 like
richardev's avatar

@BOBBYBOUWMANN - Thank you @bobbybouwmann for detailed response. The validation is red, because of HTML input attributes forcing to provide valid e-mail. Don't take it to account of solving the issue.

<input type="email" required autocomplete="email" />

Thanks for link, I will check it out and double check everything as well as dump most of the data. I will update this discussion afterwards.

P.S. I also updated original question with reset form HTML code.

bobbybouwmann's avatar

Mmh @error('email') should work fine here, as long as the validation has been done correctly of course!

richardev's avatar

@BOBBYBOUWMANN - Allright, so just had a chance to dump errors and there is no token. Here is the dump:

{"token":["The token field is required."]} 

I'll try to find at which step the token is lost, because in e-mail there is fully functional link given, yet when on reseting page, it's lost after submission.

richardev's avatar

@BOBBYBOUWMANN - It's there. That's the weird thing.

<!-- snippet from above -->
<form class="gg-login" method="POST" action="{{ route('password.update') }}">
      @csrf 
<!-- ... -->
bobbybouwmann's avatar

Yeah exactly! So if you expect the HTML in your browser you also see this hidden input field? Just checking everything now!

richardev's avatar

@BOBBYBOUWMANN - Yeah, it's there:

<input type="hidden" name="_token" value="gexH95yc1KVm71UfETtvwHcIZEvHbCtrrtscAayg">

UPDATE

Okay, so I just panicked and put dump on each step within reset function, and when I put it before validation like here:

 public function reset(Request $request)
    {
        dump($request); // <-------------------------
        $request->validate($this->rules(), $this->validationErrorMessages());

        // Here we will attempt to reset the user's password. If it is successful we
        // will update the password on an actual user model and persist it to the
        // database. Otherwise we will parse the error and return the response.
        $response = $this->broker()->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );

        // If the password was successfully reset, we will redirect the user back to
        // the application's home authenticated view. If there is an error we can
        // redirect them back to where they came from with their error message.
        return $response == Password::PASSWORD_RESET
                    ? $this->sendResetResponse($request, $response)
                    : $this->sendResetFailedResponse($request, $response);
    }

it returns the page with the Request dump and alos this message: Redirecting to http://localhost:8000/password/reset/a98f717fd25f4c5b32e2ee81e1276058e294ea6536509d1a223d131bcc0dd12c. which clearly shows that there is redirection in between validation & reset.

If I dump afterwards, it doesn't return anything, therefor it clearly shows that it breaks during $request->validate($this->rules(), $this->validationErrorMessages()); this step.

richardev's avatar
richardev
OP
Best Answer
Level 1

Allright, it seems that I've fixed it by changing html from this:

<form class="gg-login" method="POST" action="{{ route('password.update') }}">
                        @csrf 

to this:

<form class="gg-login" method="POST" action="{{ route('password.update') }}">
                        @csrf
                        <input type="hidden" name="token" value="{{ $token }}">

But honestly, I don't know why does this changes something. Isn't @csrf already containing the hidden token input?

Snapey's avatar

if validation fails then a validation error is thrown - and the next line won't be executed.

What does $this->rules contain?

dump($request->all()) does it contain the fields you are expecting in rules?

Snapey's avatar

I think the token you are missing is not the csrf token, but the password reset token. This should be passed back to the view and then included in a hidden field on the form.

The hidden form field should be called 'token'

richardev's avatar

@Snapey dump($request->all()) returns:

array:5 [▼
  "_token" => "RlwpqsSHcN42PaEWvIMHCYjjowAnox8ApTA9eeUV"
  "token" => "2fb28348a08eac8386a2276be5a7ed5a2847adef39b4ed769157e2213bc208c9"
  "email" => "[email protected]"
  "password" => "password"
  "password_confirmation" => "password"
]

Redirecting to http://localhost:8000/login. 

This works now with

<input type="hidden" name="token" value="{{ $token }}">

and while I understand what you've mentioned above about token and _token differences, I still don't understand how my solution differs from original Laravel Auth where there is just @csrf command called and then compiled by Blade returning all necessary hidden inputs.

Snapey's avatar

The boilerplate form looks like;

    <div class="card-body">
        <form method="POST" action="{{ route('password.update') }}">
            @csrf

            <input type="hidden" name="token" value="{{ $token }}">
1 like

Please or to participate in this conversation.