Password Reminders with Sentinel and Laravel

Previously I had written how users could change their passwords with Sentinel when they were logged in, making sure their current password was verified before the update occurred. Now there’s the next problem about how a user can change their password if they’ve totally forgotten. Luckily that’s built into Sentinel and is easy to implement!

I decided to create a separate repository for a simple Sentinel 2.0 and Laravel 5.1 implementation, which can be found here. All code from this tutorial is available there, relative snippets will be discussed in this post.

Again, if you haven’t setup Sentinel yet, there’s info for Laravel 5 and Laravel 4. For Laravel 5.1 I’ve just made the small change in config/app.php for my imports to look like this:

$providers array

Cartalyst\Sentinel\Laravel\SentinelServiceProvider::class,

$aliases array

'Activation' => Cartalyst\Sentinel\Laravel\Facades\Activation::class,
'Reminder'   => Cartalyst\Sentinel\Laravel\Facades\Reminder::class,
'Sentinel'   => Cartalyst\Sentinel\Laravel\Facades\Sentinel::class,

Copy the providers and facades first and then run the packages’ publish command.

I’ve setup my User model for Sentinel’s multiple login attributes like I blogged about previously.

Four new routes will be required to complete this feature.


Route::get('passwordreset/{id}/{token}', ['as' => 'reminders.edit', 'uses' => 'ReminderController@edit']);
Route::post('passwordreset/{id}/{token}', ['as' => 'reminders.update', 'uses' => 'ReminderController@update']);
Route::get('passwordreset', 'ReminderController@create');
Route::post('passwordreset', 'ReminderController@store');

Most of the main code for this password reset feature can be found in a new controller I’ve named ReminderController.


class ReminderController extends Controller
{
  public function __construct(UserRepository $userRepository)
    {
        $this->users = $userRepository;
    }

    public function create() {
        return View::make('reminders.create');
    }

    public function store() {
        $login = Input::get('login');

        $user = $this->users->findByLogin($login);

        if ($user) {
            ($reminder = Reminder::exists($user)) || ($reminder = Reminder::create($user));

            Event::fire(new ReminderEvent($user, $reminder));
        }

        return View::make('reminders.store');
    }

    ...
}

The ReminderController@create method above is used to render a form, which will ask the user to enter either their username or email to retrieve their password. Once they have completed that form, the input is sent to the ReminderController@store method. This method checks if a user with that information is present within the system, and if so creates an instance of a Reminder model. The user will have to match the code that is stored within the Reminder model, within a specific amount of time, in order to change his or her password. The method Reminder::exists($user) will return an existing Reminder object within the system if an uncompleted one already exists for that user and has not expired, otherwise it will return false. The code above will then fallback to the method Reminder::create($user) if no valid Reminder is currently present within the system for the user, which will create a new Reminder object for us to work with.

ReminderController@store throws an event which the system will listen for. A reminder email listener is registered to react to this event, which will send the user a link, to their stored email address, they can click to reset their password.


class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        'App\Events\ReminderEvent' => [
            'App\Listeners\ReminderEmailSender',
        ],
    ];

    ...
}

class ReminderEvent extends Event
{
    use SerializesModels;
    public $user;
    public $reminder;

    public function __construct(User $user, Reminder $reminder)
    {
        $this->user = $user;
        $this->reminder = $reminder;
    }
}

class ReminderEmailSender
{
    public function handle(ReminderEvent $event)
    {
        $user = $event->user;
        $reminder = $event->reminder;

        $data = [
            'email' => $user->email,
            'name' => 'Fake Name',
            'subject' => 'Reset Your Password',
            'code' => $reminder->code,
            'id' => $user->id
        ];

        Mail::queue('emails.reminder', $data, function($message) use ($data) {
            $message->to($data['email'], $data['name'])->subject($data['subject']);
        });
    }
}

Below is the email template emails.reminder. The user’s id is included in the password reset link as per Sentinel’s documentation on finding the user by id first. If you’re trying to find a user only by their Reminder code, you’ll have to write your own method to do so.

<body>
A request has recently been made to change your password.

{!! link_to_route('reminders.edit', 'Reset your password now', ['id' => $id, 'code' => $code]) !!}
</body>

When the user opens the email and clicks the link, they will activate ReminderController@edit. This action will show the user a view which will provide them an input to enter a new password, as well as a second input to confirm their new password. The Reminder::exists($user, $code) method checks if there is a non expired Reminder object for that particular user and if it matches the code that the user is sent via email. If everything goes well, the Reminder object is returned, otherwise false is returned and we’ll just exit back to the homepage.

reminders.edit View.


{!! Form::open(['reminders.update', $id, $code]) !!}
    {!! Form::password('password', array('placeholder'=>'new password', 'required'=>'required')) !!}
    {!! Form::password('password_confirmation', array('placeholder'=>'new password confirmation', 'required'=>'required')) !!}
    {!! Form::submit('Reset Password') !!}
{!! Form::close() !!}

class ReminderController extends Controller
{
  public function __construct(UserRepository $userRepository)
    {
        $this->users = $userRepository;
    }

    ...

    public function edit($id, $code) {
        $user = $this->users->findById($id);

        if (Reminder::exists($user, $code)) {
            return View::make('reminders.edit', ['id' => $id, 'code' => $code]);
        }
        else {
            //incorrect info was passed
            return Redirect::to('/');
        }
    }

    public function update($id, $code) {
        $password = Input::get('password');
        $passwordConf = Input::get('password_confirmation');

        $user = $this->users->findById($id);
        $reminder = Reminder::exists($user, $code);

        //incorrect info was passed.
        if ($reminder == false) {
            return Redirect::to('/');
        }

        if ($password != $passwordConf) {
            Session::flash('error', 'Passwords must match.');
            return View::make('reminders.edit', ['id' => $id, 'code' => $code]);
        }

        Reminder::complete($user, $code, $password);

        return Redirect::to('/');
    }
}

When the user posts back their new chosen password to ReminderController@update, again the User and Reminder objects are located by $id or $code, and if everything looks good finally the Reminder::complete($user, $code, $password); method is called to update the user’s password.

Advertisements
Password Reminders with Sentinel and Laravel

3 thoughts on “Password Reminders with Sentinel and Laravel

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s