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.
Thank you for the explanation. I’m implementing this part in a project.
LikeLike
Thanks a lot
LikeLike
Hi there no method in sentinel “findByLogin” it was in sentry how i can check a user by login to send reset reminder.
LikeLike