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

Changing a User’s Password with Sentinel and Laravel

Today I was throwing together a change password page (NOT a forgot password page, I’ll write that tutorial up shortly…) in an application using Sentinel and Laravel and thought I might as well post another tutorial on Sentinel as examples are rather hard to come by currently.

Two new routes are added to routes.php for the feature. The /resetpassword route will render our form and we will post our new password information to route /resetpasswordcomplete. These routes should only be available to users that are currently logged into the system. You can achieve this with either route filters in Laravel 4 or middleware in Laravel 5.


Route::get('resetpassword', array('as' => 'reset.password', 'uses' => 'PasswordController@edit'));
Route::post('resetpasswordcomplete', array('as' => 'reset.password.complete', 'uses' => 'PasswordController@update'));

We’ll start creating a view. This example looks for a view at views/passwords/reset.blade.php. I’m using Blade templates so it looked something like this:


@if (Session::get('error'))
  <div class="alert alert-error">
    {{ Session::get('error') }}
  </div>
@endif

{{ Form::open(array('route' => array('reset.password.complete'))) }}
  {{ Form::password('old_password', array('placeholder'=>'current password', 'required'=>'required')) }}
  {{ Form::password('password', array('placeholder'=>'new password', 'required'=>'required')) }}
  {{ Form::password('password_confirmation', array('placeholder'=>'new password confirmation', 'required'=>'required')) }}
  {{ Form::submit('Reset Password', array('class' => 'btn')) }}
{{ Form::close() }}

There’s a form field for the user’s current password, the user’s requested new password, and a confirmation for the new password.

Below is a snipped of what our PasswordController would look like, with its edit and update actions as necessary. The Sentinel facade provides the method Sentinel::getHasher() to retrieve the application’s current hashing strategy. Sentinel provides several hashing strategies documented here. The edit action just provides the form needed to reset the password and the update action checks the received info and makes sure the user entered their current password correctly as well as entered the same password in form fields password and password_confirmation.


    public function edit() {
        return View::make('passwords/reset');
    }

    public function update() {
        $hasher = Sentinel::getHasher();

        $oldPassword = Input::get('old_password');
        $password = Input::get('password');
        $passwordConf = Input::get('password_confirmation');

        $user = Sentinel::getUser();

        if (!$hasher->check($oldPassword, $user->password) || $password != $passwordConf) {
            Session::flash('error', 'Check input is correct.');
            return View::make('passwords/reset');
        }

        Sentinel::update($user, array('password' => $password));

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

That’s all for letting a user change their password while they are logged in and can remember their password. Next up I’ll write a short tutorial on how to use Sentinel’s Reminder capability for users who have forgotten their passwords.

Changing a User’s Password with Sentinel and Laravel