Setting Up a Forgot Password Form and Controller in Laravel 5.1

Access all tutorials in sprocket icon.

June 16, 2015 from author Bill Keck.

How to Setup Forgot Password in Laravel 5.1

Ok, so in my last tutorial, How to Make User Login and Registration Laravel 5.1, we setup a basic user registration and login, but we left the forgot password functionality out because the tutorial was going too long.

In Laravel 5.1, they are not including the views you need serve forgot password function out of the box. The controller and traits are in place, however.

So all we need to do is make sure that we have the route set and the proper views, and it will work perfectly.

The Route

You should have a route in app/Http/routes.php file as follows:



Route::controllers([
   'password' => 'Auth\PasswordController',
]);


If you still have the route the old AuthController, you can go ahead and delete the Auth\Authcontroller line, we will not be using it.

With this type of route, if we have for example, a getEmail function on our controller, the route will use the verb get from the function name to figure out what kind of request we are looking for.

We abandoned this approach for the login and registration controllers because we wanted a simpler approach. But since forgot password functionality is a little complicated, I’m leaving well-enough alone, especially since I don’t anticipate having to work on it in the future. It doesn’t seem likely to change, whereas I know for my registration, it could easily be different in the future, especially if I hook up social auth to it.

Ok, so we have our route in place, let’s look at the controller.

The Controller

The path is app/Http/Controllers/Auth/PasswordController.php.

It should look like this:



<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;

class PasswordController extends Controller
{
   /*
   |--------------------------------------------------------------------------
   | Password Reset Controller
   |--------------------------------------------------------------------------
   |
   | This controller is responsible for handling password reset requests
   | and uses a simple trait to include this behavior. You're free to
   | explore this trait and override any methods you wish to tweak.
   |
   */

   use ResetsPasswords;

   /**
    * Create a new password controller instance.
    *
    * @return void
    */
   public function __construct()
   {
       $this->middleware('guest');
   }
}

So where are all the actions? In the ResetsPasswords trait of course. Frankly, I don’t understand why it’s done this way. It doesn’t seem likely that the methods in the trait would be used for anything other than the forgot password feature.

Perhaps they were trying to keep the controller skinny.

The Trait

Here’s what the trait looks like:



<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Http\Request;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

trait ResetsPasswords
{
   /**
    * Display the form to request a password reset link.
    *
    * @return \Illuminate\Http\Response
    */
   public function getEmail()
   {
       return view('auth.password');
   }

   /**
    * Send a reset link to the given user.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return \Illuminate\Http\Response
    */
   public function postEmail(Request $request)
   {
       $this->validate($request, ['email' => 'required|email']);

       $response = Password::sendResetLink($request->only('email'), function (Message $message) {
           $message->subject($this->getEmailSubject());

       });

       switch ($response) {
           case Password::RESET_LINK_SENT:
               return redirect()->back()->with('status', trans($response));

           case Password::INVALID_USER:
               return redirect()->back()->withErrors(['email' => trans($response)]);
       }
   }

   /**
    * Get the e-mail subject line to be used for the reset link email.
    *
    * @return string
    */
   protected function getEmailSubject()
   {
       return isset($this->subject) ? $this->subject : 'Your Password Reset Link';
   }

   /**
    * Display the password reset view for the given token.
    *
    * @param  string  $token
    * @return \Illuminate\Http\Response
    */
   public function getReset($token = null)
   {
       if (is_null($token)) {
           throw new NotFoundHttpException;
       }

       return view('auth.reset')->with('token', $token);
   }

   /**
    * Reset the given user's password.
    *
    * @param  \Illuminate\Http\Request  $request
    * @return \Illuminate\Http\Response
    */
   public function postReset(Request $request)
   {
       $this->validate($request, [
           'token' => 'required',
           'email' => 'required|email',
           'password' => 'required|confirmed',
       ]);

       $credentials = $request->only(
           'email', 'password', 'password_confirmation', 'token'
       );

       $response = Password::reset($credentials, function ($user, $password) {
           $this->resetPassword($user, $password);
       });

       switch ($response) {
           case Password::PASSWORD_RESET:
               return redirect($this->redirectPath());

           default:
               return redirect()->back()
                           ->withInput($request->only('email'))
                           ->withErrors(['email' => trans($response)]);
       }
   }

   /**
    * Reset the given user's password.
    *
    * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
    * @param  string  $password
    * @return void
    */
   protected function resetPassword($user, $password)
   {
       $user->password = bcrypt($password);

       $user->save();

       Auth::login($user);
   }

   /**
    * Get the post register / login redirect path.
    *
    * @return string
    */
   public function redirectPath()
   {
       if (property_exists($this, 'redirectPath')) {
           return $this->redirectPath;
       }

       return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
   }
}


One obvious improvement would be to get rid of the redirectPath function and simply use our RedirectsUsers trait, since that is the sole purpose of that trait and the methods are identical.

However the ResetsPasswords trait lives in the vendor/laravel/illuminate/Foundation/Auth directory, which means when we run composer update, the file can be overwritten, which is not what we want.

We could just copy the trait into our AuthTraits folder, give it a new name and then modify it how we want, but is it worth the bother? Probably not. Like I said, we will not be messing with this code much in the future, so it’s probably best just to leave it as is.

So let’s take a brief look at the methods in this trait, keeping in mind that I didn’t write them, so I don’t know every detail, but we can still get a general understanding of how it works.

The first method simply returns the form:



public function getEmail()
{
   return view('auth.password');
}


We can tell from the view method that we are expecting an auth folder in the views folder and a password.blade.php file within the auth folder.

When the user gives us his email and submits, we post the form:



public function postEmail(Request $request)
{
   $this->validate($request, ['email' => 'required|email']);

   $response = Password::sendResetLink($request->only('email'), function (Message $message) {
       $message->subject($this->getEmailSubject());

   });

   switch ($response) {
       case Password::RESET_LINK_SENT:
           return redirect()->back()->with('status', trans($response));

       case Password::INVALID_USER:
           return redirect()->back()->withErrors(['email' => trans($response)]);
   }
}



This one gets fairly complicated when we look under the hood. There is a class named PasswordBroker that lives in vendor/laravel/framework/src/Auth/Passwords/ that has the sendResetLink method.

So the simple explanation is the method tries to find a user with that email and send them a reset link. It switches on the response, and if it is successful it redirects back with status. If not, it returns an error message for invalid user.

Exactly how it’s all stitched together with all the dependencies would make an excellent Laracasts episode. Otherwise it’s beyond the scope of this tutorial.

The next method returns the email subject:



protected function getEmailSubject()
{
   return isset($this->subject) ? $this->subject : 'Your Password Reset Link';
}


Next we have the getReset method, which returns the reset view with the token. This will be the form where they type in the new password and confirm it.



public function getReset($token = null)
{
   if (is_null($token)) {
       throw new NotFoundHttpException;
   }

   return view('auth.reset')->with('token', $token);
}

Note the use of the default of null for the token. So no need to set up a try/catch, since it will have a value in any case. It’s a nice clean alternative to a try/catch block.

Next we have postReset:




public function postReset(Request $request)
{
   $this->validate($request, [
       'token' => 'required',
       'email' => 'required|email',
       'password' => 'required|confirmed',
   ]);

   $credentials = $request->only(
       'email', 'password', 'password_confirmation', 'token'
   );

   $response = Password::reset($credentials, function ($user, $password) {
       $this->resetPassword($user, $password);
   });

   switch ($response) {
       case Password::PASSWORD_RESET:
           return redirect($this->redirectPath());

       default:
           return redirect()->back()
                       ->withInput($request->only('email'))
                       ->withErrors(['email' => trans($response)]);
   }
}


Ok, so we validate, set the credentials from the form post, use the reset method of the PasswordBroker class, which is accessed via the Password facade.

Then depending on the response, either take them to destination set by redirectPath or go back to the form with errors.

If you are interested, the Password facade class is found at vendor/laravel/framework/src/Illuminate/Support/Facades/Password.php.

There’s a method in there name getFacadeAccessor. It’s probably worth following the chain here for a moment:



protected static function getFacadeAccessor()
{
   return 'auth.password';
}


This returns the name of the component. So now we can checkout the PasswordResetServiceProvider class, which you can find at:

vendor/framework/src/Illuminate/Auth/Passwords/PasswordResetServiceProvider.php

Let’s look at one method there:



protected function registerPasswordBroker()
{
   $this->app->singleton('auth.password', function ($app) {
       // The password token repository is responsible for storing the email addresses
       // and password reset tokens. It will be used to verify the tokens are valid
       // for the given e-mail addresses. We will resolve an implementation here.
       $tokens = $app['auth.password.tokens'];

       $users = $app['auth']->driver()->getProvider();

       $view = $app['config']['auth.password.email'];

       // The password broker uses a token repository to validate tokens and send user
       // password e-mails, as well as validating that password reset process as an
       // aggregate service of sorts providing a convenient interface for resets.
       return new PasswordBroker(
           $tokens, $users, $app['mailer'], $view
       );
   });
}

This is kind of complicated, too much for a beginning tutorial. But the main thing I wanted you to see is that ‘auth.password’ is bound to PasswordBroker. So, since the Password facade is linked to auth.password and auth.password is bound to PasswordBroker, we end up using the methods of PasswordBroker when we call Password.

If you read my service provider tutorial, which was simple, you get some sense of this, and here we have a very advanced example. I should also say it’s extremely well-commented.

If you are a beginner and all this seems like being dropped in the middle of an ocean without a lifejacket or raft, don’t worry, we’ve all been there. Just stay with it, eventually, you will get it.

You don’t have to memorize it all to make progress. Eventually, enough will stick so that you can use what you have as a knowledge base that you can build on. That’s how most of us do it.

Ok, heading back to our ResetsPasswords trait, we need to look at the resetPassword method:



protected function resetPassword($user, $password)
{
   $user->password = bcrypt($password);

   $user->save();

   Auth::login($user);
}

This is the method that sets and saves the new password and logs in the user. We use in our postReset method.

The final method is redirectPath, which we have already covered.

You can see just how involved the process of resetting a password is, and you can see why I didn’t want to build it from scratch or mess with it too much.

The Views

The last step to make our forgot password functionality operational is to create some views. Let’s start by creating the two folders that we will need, which will reside in the views folder. Make an emails folder and an auth folder inside the views folder.

Lets make a password.blade.php file and place it in the emails folder with the following contents:



Click here to reset your password: {{ url('password/reset/'.$token) }}

Things had to get easier sooner or later… You can of course add what ever messaging you want to this view. This is the email that will be sent to the user with the link they need to reset their password.

Next, we need to create reset.blade.php and place it within the auth folder with the following contents:



@extends('layouts.master')

@section('content')

<div class="container-fluid">
<div class="row">
 <div class="col-md-8 col-md-offset-2">
 <div class="panel panel-default">
 <div class="panel-heading">Reset Password</div>
 <div class="panel-body">

 @if (count($errors) > 0)
    <div class="alert alert-danger">
    <strong>Whoops!</strong> There were some problems with your input.<br><br>
    <ul>
        @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
        @endforeach
         </ul>
        </div>
@endif

<form class="form-horizontal" role="form" method="POST" action="/password/reset">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="token" value="{{ $token }}">

<div class="form-group">
<label class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input type="email" class="form-control" name="email" value="{{ old('email') }}">
</div>
</div>

<div class="form-group">
<label class="col-md-4 control-label">Password</label>
<div class="col-md-6">
<input type="password" class="form-control" name="password">
</div>
 </div>

 <div class="form-group">
 <label class="col-md-4 control-label">Confirm Password</label>
 <div class="col-md-6">
 <input type="password" class="form-control" name="password_confirmation">
 </div>
  </div>

 <div class="form-group">
 <div class="col-md-6 col-md-offset-4">
 <button type="submit" class="btn btn-primary">
             Reset Password
 </button>
 </div>
 </div>
 </form>

 </div>
 </div>
 </div>
 </div>
</div>

@endsection

This view is just the form for the password reset, so they can enter the new password.

And finally, we need to create a file named password.blade.php in the auth folder with the following contents:



@extends('layouts.master')

@section('content')

<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Reset Password</div>
<div class="panel-body">
    @if (session('status'))
      <div class="alert alert-success">
             {{ session('status') }}
      </div>
    @endif

    @if (count($errors) > 0)
       <div class="alert alert-danger">
       <strong>Whoops!</strong> There were some problems with your input.<br><br>
       <ul>
           @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
           @endforeach
        </ul>
        </div>
      @endif

<form class="form-horizontal" role="form" method="POST" action="/password/email">
<input type="hidden" name="_token" value="{{ csrf_token() }}">

<div class="form-group">
<label class="col-md-4 control-label">E-Mail Address</label>
<div class="col-md-6">
<input type="email" class="form-control" name="email" value="{{ old('email') }}">
</div>
</div>

<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
         Send Password Reset Link
</button>
</div>
</div>
</form>

</div>
</div>
</div>
</div>
</div>

@endsection


This is another very simple form. Users enter their email and it posts to the postEmail method on the ResetsPasswords trait.

And that is all we need. If you click on the forgot password link in your login form, assuming you followed my How to Make User Login and Registration Laravel 5.1, you will then be taken to the auth.password view to do the lookup on email address and cause the email to be sent.

Then assuming the user receives the email, it will be formatted with the emails.password view, so they can click that link and get them to the reset view, where they can enter the new password.

We don’t cover configuration for sending an actual email in this tutorial, but if you go to app/config/mail.php, there is a setting you can change at the bottom of the file:



'pretend' => true,

 

When it’s set to true, it will send an email to your application log files, located at app/storage/logs/laravel.log.

You can test your forgot password implementation to the point where it will make an entry in the log file.

That’s gonna do it for this tutorial, I hope you enjoyed. Please comment, share, and like if you can, thanks!

I don’t have a donate button, but If you would like to support my work, you can do so by buying one of my 99¢ books, I really appreciate it.

50 thoughts on “Setting Up a Forgot Password Form and Controller in Laravel 5.1

  1. Esteban says:

    Great tutorials! I hope many people can benefit from this tutorials. I have a problem with the reset password. It seems the migrations put a foreign key in the password_resets table and I have been looking where is the save statement for this. Hope anyone can help.

    Like

  2. Thanks Esteban! I didn’t see a foreign key in the migration for password_resets that comes out of the box with Laravel 5.1, so I’m not sure what you are referring to there.

    Like

  3. Esteban says:

    Nevermind that was a mistake of mine since I used profiles with the build in Laravel functionalities.
    Thanks anyway! By the way I ended up creating my own trait so I can personalize the language!

    Like

    • If you are following this tutorial, you need the following route:
      Route::controllers([
      ‘password’ => ‘Auth\PasswordController’,
      ]);

      With this method of doing it, the type of route is identified on the controller action by the verb, get or post. Please read the full tutorial for details.

      Like

    • Hi Daniel. I can’t offer personalized support. I’m in the end phase of writing a book at the moment. I will be expanding the tutorials on Laravel Tips after I’m done. I hope to get back to it in about 2 weeks.

      Laravel has a great forum and community for support. I recommend posting there if you need further assistance.

      Like

      • and Routes are
        // Authentication routes…
        Route::get(‘auth/login’, ‘Auth\AuthController@getLogin’);
        Route::post(‘auth/login’, ‘Auth\AuthController@postLogin’);
        Route::get(‘auth/logout’, ‘Auth\AuthController@getLogout’);

        // Registration routes…
        Route::get(‘auth/register’, ‘Auth\AuthController@getRegister’);
        Route::post(‘auth/register’, ‘Auth\AuthController@postRegister’);

        Route::controllers([
        ‘password’ => ‘Auth\PasswordController’,
        ]);
        if i change action to the following
        action=”auth/login” receive the exception NotFoundHttpException in RouteCollection.php line 161:

        Like

  4. chinlam91 says:

    HI bill , nice to meet you . I’m the newbie at Laravel 5 .
    I have read your tutorial, and just something confuse that i dont know where i should put the Click ## here to reset your password: {{ url(‘password/reset/’.$token) }} ##. Thanks for your tutorial and have a nice day 🙂

    Like

  5. James says:

    I’m relatively new to Laravel and am trying to figure out the reset password process. It works fine to reset a password, however my home view contains some data that is pulled in from the route when it is navigated to, which is circumvented through the reset process by returning the home view. If I wanted to return to a route instead, how would I do this?
    I’ve temporarily got around this by preventing the user from being logged in and the middleware just redirects them to log in, however this isn’t ideal long term.

    Ideally, I’d like for their password to be reset, then have them redirected to my HomeController so that the data can be pulled in when the home view is returned, with a flash message to advise that the password has been reset.

    Any ideas on how to implement this?

    Like

    • Without digging in too deep, I think you could overwrite the redirectPath method on the PasswordController. The redirectPath method is actually in the ResetsPasswords trait, which the Password controller uses, so you can overwrite this on the PasswordController to specify the route you want in this particular scenario.

      By overwriting the method, you don’t have to change anything in the framework code, so you don’t have to worry about losing code when you do composer update. I hope that helps.

      Like

  6. Hello Bill
    Thank you for detail series for password reset . I have made few changes the in user table`s column email to company_email and now user registration and login works fine but password not working . It is giving
    SQLSTATE[42S22]: Column not found: 1054 Unknown column ’email’ in ‘where clause’ (SQL: delete from `password_resets` where `email` is null)

    I had changed password_resets email column to company_email but still it is giving the above error

    Like

  7. now my login working my login fine but it not recognize the password i manually inserted in my database
    After pressing submit button it gives me
    Login
    Whoops! There were some problems with your input.

    These credentials do not match our records.

    Like

    • ok, to answer both of your questions, the password needs to be encrypted, which is why if you enter it manually without encryption, it will not work. Second, the redirectTo property tells it where to go after login and register. If you don’t set that, it defaults to home, but there is no home, so you will get an error.

      Like

  8. after providing email address and clicking on send a reset link button it shows We have e-mailed your password reset link!. But I didn’t get mail. also i have set true as ‘pretend’ => true,

    Like

    • When pretend is set to true, it will not send an email, but it will log to the logs folder in your storage directory. When you set pretend to false, you will also have to configure you email settings in config/email.php, but that is beyond the scope of the tutorial.

      Like

  9. when i press forgot password and sending email successfully i want to go to this path :
    Click here to reset your password: {{ url(‘password/reset/’.$token) }} in email folder in views.
    but this link is not displaying to me after sending email.

    Like

  10. Mike says:

    Great tutorial!
    New using Laravel and when I tried to visit the page for the password reset (“Click here to reset your password”) I’m getting an error saying undefined variable: token. How can I correct this?

    Like

    • Interesting because I think you should get a NotFoundHttpException if the token is not set. So is it possible you have confused the reset page with the password.blade.php view?

      Like

  11. Thanks for this tutorial. In my email view, I have something like Click to Reset Password

    But when the link is clicked, I instead get http://localhost/password/reset/d46949c7281583b60a15c915622335bce9525994677660bb88d114f60a21bd3a and not the complete URL. The complete URL should have been http://localhost/oap_eneo/public/password/reset/46a19271b866a719b98918ec00c4b0e178fddc7e29bb08eb127d0e0674de57f6. What could be the problem (oap_eneo/public is missing)

    Like

  12. Thank you so much for writing this! I’m working on my first Laravel app and took the same approach of not using the auth or registering controllers from the framework. But I didn’t have any idea about how to manage the password reset part, at least until now!

    Like

  13. karthikeyan says:

    Hi,
    i am getting “NotFoundHttpException in RouteCollection.php line 161:” when clicking any link(Login,Register,forgot password)

    Like

  14. karthikeyan says:

    Thank you for your responce,after fix that issue, when try to make registration it shows error PDOException in Connector.php line 55:
    SQLSTATE[HY000] [1045] Access denied for user ‘homestead’@’localhost’ (using password: YES)

    Like

Leave a comment