iABlog

Implementing Password Reset via API in Laravel

26 Aug 2018 | laravel, php, vuejs, laravel-passport, api

I recently implemented Laravel Passport in a Laravel project that utilizes Vue.js. This project also requires a password reset feature. The bad news is, Laravel's built in password reset feature was only meant for conventional blade templates. However, the good news is most of the password reset features could still be used with a few modifications.

Step 0: Prerequisites

You need to have the following stuff implemented/installed:

If you run php artisan migrate on a fresh install of Laravel then you would be able to fulfill the above requirements.

Step 1: Define Routes

We will be using the ForgotPasswordController and ResetPasswordController in the App\Http\Controllers\Authdirectory. If you had followed the instructions in the official docs and ran the php artisan command then everything would automatically be done for you, however by doing so other unnecessary routes and views would be created as well so we would just manually define two routes, one to generate a reset token and send a reset link to the user via email; and another to reset the password with the generated token like so:

// \routes\api.php

// Add the following to your existing routes
Routes::prefix('password')->group(function() {
    Routes::post('email', 'Auth\ForgotPasswordContoller@sendResetLinkEmail');
    Routes::post('reset', 'Auth\ResetPasswordController@reset');
});

Step 2: Override Controller Methods

Just by referring to the routes that we define earlier, it is understood that we need to define a sendResetLinkEmail() method in ForgotPasswordController::class and a reset() method in ResetPasswordController::class. Upon first look these do not seem to be implemented in their respective classes, but they are actually defined in the traits that are declared:

class ForgotPasswordController extends Controller
{

    use SendsPasswordResetEmails; # <- this right here!

    ...

}

Let's take a closer look at the original methods:

// Illuminate\Foundation\Auth\SendsPasswordResetEmails
public function sendResetLinkEmail(Request $request)
{
    $this->validateEmail($request);
    // We will send the password reset link to this user. Once we have attempted
    // to send the link, we will examine the response then see the message we
    // need to show to the user. Finally, we'll send out a proper response.
    $response = $this->broker()->sendResetLink(
        $request->only('email')
    );
    return $response == Password::RESET_LINK_SENT
            ? $this->sendResetLinkResponse($response)
            : $this->sendResetLinkFailedResponse($request, $response);
}





// Illuminate\Foundation\Auth\ResetsPasswords
public function reset(Request $request)
{
    $this->validate($request, $this->rules(), $this->validationErrorMessages());
    // Here we will attempt to reset the user's password. If it is successful we
    // will update the password on an actual user model and persist it to the
    // database. Otherwise we will parse the error and return the response.
    $response = $this->broker()->reset(
        $this->credentials($request), function ($user, $password) {
            $this->resetPassword($user, $password);
        }
    );
    // If the password was successfully reset, we will redirect the user back to
    // the application's home authenticated view. If there is an error we can
    // redirect them back to where they came from with their error message.
    return $response == Password::PASSWORD_RESET
            ? $this->sendResetResponse($response)
            : $this->sendResetFailedResponse($request, $response);
}

As you can see, both methods return a response depending on whether the operation was a success. However the response is not a JSON response, but a view response. As an API wouldn't work if the response is a redirected page, we would override the method and change the return values:

// App\Http\Controllers\Auth\ForgotPasswordController
class ForgotPasswordController extends Controller
{
    ...

    public function sendResetLinkEmail(Request $request)
    {
        $this->validateEmail($request);
        // We will send the password reset link to this user. Once we have attempted
        // to send the link, we will examine the response then see the message we
        // need to show to the user. Finally, we'll send out a proper response.
        $response = $this->broker()->sendResetLink(
            $request->only('email')
        );
        return $response == Password::RESET_LINK_SENT
            ? response->json(['message' => 'Reset Link has been sent to your email.'], 200)
            : response->json(['message' => 'Fail to send password reset email'], 500);
    }
}





// App\Http\Controllers\Auth\ResetPasswordController
class ResetPasswordController extends Controller
{
    public function reset(Request $request)
    {
        $this->validate($request, $this->rules(), $this->validationErrorMessages());
        // Here we will attempt to reset the user's password. If it is successful we
        // will update the password on an actual user model and persist it to the
        // database. Otherwise we will parse the error and return the response.
        $response = $this->broker()->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );
        // If the password was successfully reset, we will redirect the user back to
        // the application's home authenticated view. If there is an error we can
        // redirect them back to where they came from with their error message.
        return $response == Password::PASSWORD_RESET
            ? response->json(['message' => 'Password successfully reset.'], 200)
            : response->json(['message' => 'Fail to reset password'], 500);
    }
}

Step 3: Customize the password reset email

To successfully send out the email, you would need to define a named route called password.reset. If you would like Vue.js to handle that for you, just point it to the blade template file that Vue.js is initialized:

// route/web.php

Route::get('password/reset', function() {
    return view('index'); // Replace index with the name of the view where Vue.js is initialized
})->name('password.reset');

The URL of the reset link would be http://example.com/password/reset?[reset_token], and of course you can change the URL to whatever you want.

If you're planning to customize the passsword reset email, you can do so by creating a new notification class and override the sendPasswordResetNotification() method in your User model class, as shown in the official docs. You would still need to define a password reset route, but you can omit the route name method if you're hardcoding the reset URL in the email template.

Step 4: Test your shiny new API

So now the hardest part is over, its time to test and see if everything's working! Try out your new APIs with your favorite API testing tool (Postman etc.) and you should similar results below:

Send password reset request

// POST /api/password/forgot
{
    "email": "user@example.com" // Make sure this email exists in your Users table!
}

// Response 200 OK
{
    "message": "Reset Link has been sent to your email."
}

You should receive an email containing a password reset link. If you're using Laravel Homestead in your local environment, you can configure mailhog and view the emails sent from Laravel at http://localhost:8025.

Reset Password

// POST /api/password/reset
{
    "password": "foobar", // 6 characters minimum by default
    "password_confirmation": "foobar",
    "token": [reset_token goes here]
}

// Response 200 OK
{
    "message": "Password successfully reset."
}

References


Adwin Ying's avatar
Adwin Ying

Self-taught full-stack web dev based in Tokyo. Occasionally wrecks servers through self-hosting and homelab-ing.

← Back to all posts