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.