Laravel-style Password Resets in Lumen

2016

So, you're building an API with Lumen and you want to add some basic password reset functionality. That should be easy, right? After all, adding password resets in Lumen's older sibling, Laravel, is a cinch. Yeah, I thought it would be easy too, but it wasn't. After frantic googling returned a Laracasts thread with no response, a Laravel.io thread with no response, and no other relevant results, I knew I was on my own for this one. After hours of questioning my career choice, I finally figured it out. Hopefully this blog post will help you avoid the struggle.

Disclaimers:

  1. I am writing this blog in May of 2016, using Lumen version 5.2. If you are reading this in the year 3000 and using Lumen version 112, some rejiggering may be necessary.
  2. I am assuming you have Lumen's built in User stuff configured (namely, a "User" model and a "users" database table).
  3. I am assuming you have intermediate knowledge of Laravel/Lumen.

Anyway, let's start with the basics. We're going to need to add 2 new packages via composer: illuminate/mail and guzzlehttp/guzzle. So let's do that. Pop open the command line and run:

composer require guzzlehttp/guzzle

and then

composer require illuminate/mail

If you did that correctly, you should have 2 new lines in your composer.json files "require" section that look something like this:

"illuminate/mail": "^5.2",
"guzzlehttp/guzzle": "~4.0"

Don't forget to run composer update after you do that, too.

Of course, we can't have password resets without somewhere to store the tokens. So let's create a password resets migration via the command line with artisan.

php artisan make:migration create_passwords_resets_table

Laravel provides scaffolding for this, but Lumen doesn't. That's alright though, just copy the migration code from Laravel into the new file you just created and re-run your migrations:

php artisan migrate

If all went well, that should have created a new password_resets table in your database.

While we're already copying files over from Laravel, let's grab a few more we'll need:

  1. Create a config/mail.php file and copy the contents over from Laravel.
  2. Create a config/services.php file and copy the contents over from Laravel.
  3. Create a config/auth.php file and copy the contents over from the Lumen Framework.
  4. Create a resources/views/auth/emails/password.blade.php file and copy the contents over from Laravel.

Next, head over to your bootstrap/app.php file and uncomment the following lines (if you haven't already):

  1. $app->withFacades();
  2. $app->withEloquent();
  3. $app->register(App\Providers\AppServiceProvider::class);

Now, let's add a few lines to that same file:

  1. Add $app->configure('services'); right below $app->withEloquent();.
  2. Add $app->configure('mail'); right below the line you just added.
  3. Add $app->register(\Illuminate\Auth\Passwords\PasswordResetServiceProvider::class); right below $app->register(App\Providers\AppServiceProvider::class);
  4. Add $app->register(\Illuminate\Mail\MailServiceProvider::class); right below the line you just added.

Great, now let's head on over to that config/auth.php file you created a few steps ago. If you scroll to the bottom, you should see code like this:

'passwords' => [
]

Let's change it to this:

'passwords' => [
    'users' => [
        'provider' => 'users',
        'email' => 'auth.emails.password',
        'table' => 'password_resets',
        'expire' => 60,
    ],
]

The "provider" key there will actually be equal to whatever you set as your provider in the "providers" array of that same file. I called mine users, and it looks like this:

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => AppUser::class,
    ],
]

Yours might be different, and that's fine, as long as they're the same throughout your code.

Alright, we're doing great! Almost done. Let's head over to your User model, because we need to make 2 small changes there:

  1. You need to make the model itself implement the Illuminate\Contracts\Auth\CanResetPassword interface.
  2. You need to pull in the Illuminate\Auth\Passwords\CanResetPassword trait.

Laravel includes these by default, but Lumen does not. When you're done with that, your model should look something like this:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements CanResetPasswordContract
{
  use CanResetPassword;
}

At this point, all of the pieces are in play. But we still have a major step left, we need to create a new trait called "ResetsPasswords". You can create that anywhere under your app directory. Now, this part is admittedly the trickiest, only because it involves the most amount of customization from you.

At first, you can just paste in the contents from Laravel's ResetPasswords trait (just don't forget to update the namespace to your own namespace). However, this file really can't work directly copied from Laravel, because it makes use of things that wouldn't be of much use to you in an API. For instance, several of the methods return redirects. Redirects aren't a thing in APIs. So you'll need to do some messing around with it to fit your needs. Here's a slimmed down version of the one I'm using to get you started. As you can see, it's mostly a direct copy of the one Laravel provides, just with some changes to the responses and the removal of the methods that just returned views.

Ok! The heavy lifting is over. Let's do our last step and create a PasswordController that just includes our new ResetsPassword trait:

namespace App\Http\Controllers;

use App\ResetsPasswords;

class PasswordController extends ApiController
{
  use ResetsPasswords;

  public function __construct()
  {
    $this->broker = 'users';
  }
}

Side Note: I really hate setting that broker variable in the PasswordController constructor function, but I haven't figured out the better solution just yet. It'll do for now!

And finally, don't forget to add your routes!

$app->post('/password/email', 'PasswordController@postEmail');
$app->post('/password/reset/{token}', 'PasswordController@postReset');

That's it! You now have password resets in your Lumen application. I realize that this tutorial doesn't cover everything. For instance, you still need to setup your mail.php file with your mail drivers configuration. But hopefully, at the very least, this gets you creating some errors that you can actually understand.