; Multi Auth in Laravel 5.4, Chapter III : Reset Password
Back to blog

Multi Auth in Laravel 5.4, Chapter III : Reset Password

February 05, 2017

Chapter 3 :: Reset Password

If you have followed along the previous chapters, you might have reached a point where you are able to login/register with a model of your choice.(Other than laravel’s default user model). But now the real problem arises, how do we reset password for persons getting authenticated to system with this new model (Seller in our case).

TL;DR

If you are getting bored and do not have patience or will to follow the rest of chapter. Then you can refer the repository. Check the commit history for details.

Repository for Multi Auth in Laravel

How it works for laravel

Laravel resets password with password brokers, we will discuss about creation of our custom broker for seller model in the later part of the chapter. I would suggest you to go through the documentation

Password Reset in laravel

Notifications in laravel

Setting Expectations

Like every time, let’s first set our expectations in routes file


//other routes

Route::group(['middleware' => 'seller_guest'], function() {
Route::get('seller_register', 'SellerAuth\RegisterController@showRegistrationForm');
Route::post('seller_register', 'SellerAuth\RegisterController@register');
Route::get('seller_login', 'SellerAuth\LoginController@showLoginForm');
Route::post('seller_login', 'SellerAuth\LoginController@login');

//Password reset routes
Route::get('seller_password/reset', 'SellerAuth\ForgotPasswordController@showLinkRequestForm');
Route::post('seller_password/email', 'SellerAuth\ForgotPasswordController@sendResetLinkEmail');
Route::get('seller_password/reset/{token}', 'SellerAuth\ResetPasswordController@showResetForm');
Route::post('seller_password/reset', 'SellerAuth\ResetPasswordController@reset');

//other routes
});

If you have noticed, i have placed password reset routes in the group with seller_guest middleware, because we do not want logged in user/seller accessing them. Only a non-logged in person should access them.

Accomplish the Expectations

Ok.. Let’s create our ForgotPasswordController. This controller will be responsible for sending password reset notifications to seller.

php artisan make:controller SellerAuth/ForgotPasswordController

Now let’s write the logic for our first route Route::get('seller_password/reset', 'SellerAuth \ForgotPasswordController @showLinkRequestForm');

We will be using SendsPasswordResetEmails trait to simply few steps in our ForgotPasswordController.

//ForgotPasswordController.php

namespace App\Http\Controllers\SellerAuth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

//Trait
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

class ForgotPasswordController extends Controller
{
    //Sends Password Reset emails
    use SendsPasswordResetEmails;
}

If we look at the various methods of this trait which can be found at vendor/laravel/framework /src/Illuminate/Foundation/Auth, we can observe that showLinkRequestForm() method is already defined, but it returns the view used by laravel’s default user. Therefore, we need to override this method in our ForgotPasswordController.

//ForgotPasswordController.php

namespace App\Http\Controllers\SellerAuth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

//Trait
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

class ForgotPasswordController extends Controller
{
    //Sends Password Reset emails
    use SendsPasswordResetEmails;

    //Shows form to request password reset
    public function showLinkRequestForm()
    {
        return view('seller.passwords.email');
    }
}

Now let’s create the email view in the resources/views/seller/passwords folder.

Email View


//email.blade.php

@extends('seller.layouts')

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

                    <form class="form-horizontal" role="form" method="POST" action="{{ url('/seller_password/email') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}" required>

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

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

We have created a view where seller can request password reset link which shall be sent to their registered email. Now let’s work on handling the data POST’ed from this view with sendResetLinkEmail() method, for which we have already defined the route Route::post('seller_password/email', 'SellerAuth \ForgotPasswordController @sendResetLinkEmail');

But wait, SendsPasswordResetEmails trait declared inside our ForgotPasswordController has already defined sendResetLinkEmail() method. Let’s look into this method and analyze each step taken by it.

  1. Method validates seller’s request and checks whether email is provided by seller. So, there is no need to modify this step.
  2. Method calls for a broker() method and uses sendResetLink() to send password reset link and stores the result in $response variable. The broker() method defined in this trait returns default user’s password broker. But we want to reset password of seller. Therefore we need to override broker() method in our ForgotPasswordController. We will discuss about sendResetLink() method later.
  3. Method evaluates $response variable, if response is equal to Password::RESET_LINK_SENT , then system sends back success response. If not, then it returns the error message.

After analyzing the above steps, we have concluded that we need to override broker() method in our ForgotPasswordController.

//ForgotPasswordController

namespace App\Http\Controllers\SellerAuth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

//Trait
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

//Password Broker Facade
use Illuminate\Support\Facades\Password;

class ForgotPasswordController extends Controller
{
    //Sends Password Reset emails
    use SendsPasswordResetEmails;

    //Shows form to request password reset
    public function showLinkRequestForm()
    {
        return view('seller.passwords.email');
    }

    //Password Broker for Seller Model
    public function broker()
    {
         return Password::broker('sellers');
    }
}

The broker() method returns our own custom password broker for seller model which we have to create now.

For this, let’s open config/auth.php and append our password broker to passwords array

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        //Seller password broker
       'sellers' => [
            //user provider for seller
           'provider' => 'sellers',
            //table to store password reset tokens for seller
           'table' => 'seller_password_resets',
           //expire time for these tokens in minutes
           'expire' => 60,
       ],
    ],

In our password broker we mentioned our user provider (seller), expire time of our tokens and a table which stores these token.

The next step would be creation of seller_password_resets table. Let’s create migration for this table

php artisan make:migration create_sellers_password_reset_table --create="seller_password_resets"

Open the migrations file and add the email and token columns.


    public function up()
    {
        Schema::create('seller_password_resets', function (Blueprint $table) {
          $table->string('email')->index();
          $table->string('token')->index();
          $table->timestamp('created_at')->nullable();
        });
    }

    public function down()
    {
        Schema::dropIfExists('seller_password_resets');
    }

Now let’s migrate this table

php artisan migrate

Lets look again at SendsPasswordResetEmails trait, as explained earlier the broker() calls for sendResetLink() method in the second step of sendResetLinkEmail() method. So what is this method and where does it comes from.

The method sendResetLink() is defined in the PasswordBroker class of vendor/laravel/src /framework/Illuminate /Auth/Passwords folder. Let’s look into this method and evaluate the steps

  1. It uses getUser() method to check if seller is present in database and if the model is an instance of CanResetPasswordContract interface. It fetches the model data and stores to $user variable. So we need to handle this part in our Seller model class. If not, exception/error will be displayed.
  2. It calls sendPasswordResetNotification on $user variable and passes newly generated token in it. We also need to handle this part in our Seller model class.
  3. Lastly it returns response informing us that reset password link has been sent to the email.

So there are two things we need to do, our Seller Model should implement CanResetPasswordContract and should have sendPasswordResetNotification() defined.

Don’t be scared. We have already extended the class Illuminate\Foundation\Auth\User in Seller model in our first chapter (Registration). This class has already implemented CanResetPasswordContract interface and uses Illuminate\Auth \Passwords\CanResetPassword trait which has the declared sendPasswordResetNotification() method.

In this Illuminate\Auth \Passwords\CanResetPassword trait, the method sendPasswordResetNotification() notifies with ResetPasswordNotificationclass. But wait, this is the notification is for default User not Seller.

To avoid ambiguous situation, we need to override sendPasswordResetNotification() method in seller model and have the notification sent to our own custom notification class. So, let’s first create SellerResetPasswordNotification class.

php artisan make:notification SellerResetPasswordNotification

Open this class SellerResetPasswordNotification which is placed at app/Notifications folder and add content which should include url to for our next route Route::get('seller_password/reset /{token}', 'SellerAuth\ResetPasswordController @showResetForm');

//SellerResetPasswordNotification.php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;

class SellerResetPasswordNotification extends Notification
{
    //Places this task to a queue if its enabled
    use Queueable;

    //Token handler
    public $token;

    public function __construct($token)
    {
        $this->token = $token;
    }

    //Notifications sent via email
    public function via($notifiable)
    {
        return ['mail'];
    }

    //Content of email sent to the Seller
    public function toMail($notifiable)
    {
        return (new MailMessage)
        ->line('You are receiving this email because we received a password reset request for your account.')
        ->action('Reset Password', url('seller_password/reset', $this->token))
        ->line('If you did not request a password reset, no further action is required.');
    }

}

We have created a notification class for seller. Now let’s complete the pending task, which is overriding sendPasswordResetNotification() method in Seller model. Open app/Seller.php, add this method and pass the token.

//seller.php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;

//Trait for sending notifications in laravel
use Illuminate\Notifications\Notifiable;

//Notification for Seller
use App\Notifications\SellerResetPasswordNotification;

class Seller extends Authenticatable
{

 // This trait has notify() method defined
  use Notifiable;

  //Mass assignable attributes
  protected $fillable = [
      'name', 'email', 'password',
  ];

  //hidden attributes
  protected $hidden = [
      'password', 'remember_token',
  ];

  //Send password reset notification
  public function sendPasswordResetNotification($token)
  {
      $this->notify(new SellerResetPasswordNotification($token));
  }
}

Lets test by using forgot password link to send email to exiting seller.

Forgot_Password_Link Forgot_Password_Page

Received Email

Now clicking on the password reset link mentioned in email will land seller us on our next route Route::get('seller_password/reset /{token}', 'SellerAuth \ResetPasswordController @showResetForm');

Let’s create the ResetPasswordController.

php artisan make:controller SellerAuth/ResetPasswordController

We shall use ResetsPasswords trait placed at vendor/laravel /framework/src/llluminate /foundation/Auth in our ResetPasswordController. This trait has all the methods we need to reset seller’s password. We might need to override few methods.

//ResetPasswordController.php

namespace App\Http\Controllers\SellerAuth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

use Illuminate\Foundation\Auth\ResetsPasswords;

class ResetPasswordController extends Controller
{
    //trait for handling reset Password
    use ResetsPasswords;
}

If we look into ResetsPasswords trait we can observe that showResetForm() used in route is already defined but it returns view used for resetting default user’s password. So we need to override this method in our ResetPasswordController and return reset password view for seller.

//ResetPasswordController.php

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

use Illuminate\Foundation\Auth\ResetsPasswords;

class ResetPasswordController extends Controller
{
    //trait for handling reset Password
    use ResetsPasswords;

    //Show form to seller where they can reset password
    public function showResetForm(Request $request, $token = null)
    {
        return view('seller.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }
}

We need to create a new view reset.blade.php at resources/views/seller/passwords folder.

Reset View

@extends('seller.layouts')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-success">
                <div class="panel-heading">Reset Password</div>

                <div class="panel-body">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif

                    <form class="form-horizontal" role="form" method="POST" action="{{ url('/seller_password/reset') }}">
                        {{ csrf_field() }}

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

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ $email or old('email') }}" required autofocus>

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
                            <label for="password-confirm" class="col-md-4 control-label">Confirm Password</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>

                                @if ($errors->has('password_confirmation'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password_confirmation') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

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

Now let’s handle the data POST’ed from this form, for which we have defined route Route::post('seller_password/reset', 'SellerAuth \ResetPasswordController@reset');

The ResetsPasswords trait used in our ResetPasswordController already has reset() defined. Let’s look into this method and analyze each step taken by this method.

  1. It validates the form request to check whether all the necessary fields are present in the request or not. No need to modify this step.
  2. It calls the reset() method on the password broker which resets password of seller and persists it in sellers database. The broker() method of this class returns laravel’s default user’s password broker. Therefore we need to override this method in our ResetPasswordController class.
  3. It authenticates the Seller with guard() method. The guard() method returns laravel’s default user’s authentication guard. Therefore we need to override this method in our ResetPasswordController class.
  4. If password reset is successful, then seller is redirected to $redirectTo path. We need to define this property within our ResetPasswordController class.
  5. If password reset fails, seller will be redirected back to the reset view and shown an error message.

So, we have concluded that we need to override broker() and guard() method and declare $redirectTo property in ResetPasswordController class.

//ResetPasswordController.php

namespace App\Http\Controllers\SellerAuth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

use Illuminate\Foundation\Auth\ResetsPasswords;

//Auth Facade
use Illuminate\Support\Facades\Auth;

//Password Broker Facade
use Illuminate\Support\Facades\Password;

class ResetPasswordController extends Controller
{
    //Seller redirect path
    protected $redirectTo = '/seller_home';

    //trait for handling reset Password
    use ResetsPasswords;

    //Show form to seller where they can save new password
    public function showResetForm(Request $request, $token = null)
    {
        return view('seller.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }

    //returns Password broker of seller
    public function broker()
    {
        return Password::broker('sellers');
    }

    //returns authentication guard of seller
    protected function guard()
    {
        return Auth::guard('web_seller');
    }
}

End of Chapter

Hurray.. We are Done... Let’s test out what we accomplished.