In Depth: Adding Rehashing to Laravel

[InDepth#20] It turns out Laravel was missing an important piece of it's Authentication system: password rehashing! Let's add that in and learn how the authentication system works in the process.

In Depth: Adding Rehashing to Laravel

As I talked about in last week’s security tip, it is important to increase bcrypt rounds over time to keep hashed updated and following best practices. However, since we can only rehash1 passwords when we have the raw password, we need to do this during login-time for existing users. This is a relative simply process which should be implemented in all authentication systems.

I was confident that this was being handled in Laravel already - I’m sure I’d seen it in there somewhere2, but some helpful folks pointed out they couldn’t find it and sure enough, it was missing! While this should definitely be considered a security issue, it’s only a minor risk as rehashing passwords is a proactive measure and bcrypt hashes with `rounds=10` are still incredibly secure, so we have plenty of time to fix this issue in Laravel.

To fix this issue, we’re going to start with Laravel Breeze and work our way down into the core of the authentication system. This should give you a comprehensive overview of how Laravel’s authentication system works, since it’s important to know how authentication works in your own app, and we’ll find the best place to implement rehashing. Once we’ve found that, we’ll create the PR and submit it.

As part of this dive, we’re looking for somewhere that meets 3 requirements:

  1. Knows the plaintext (raw) password.
  2. Knows the existing password hashed.
  3. Contains the User model and has access to modify it safely.

Once we’ve found that, it’s just a simple case of writing the code, committing it, and making a PR to get it into Laravel and out to everyone! What could possibly go wrong? (Spoiler alert: Something goes wrong…)

For those who like spoilers, here are my two PRs:
https://github.com/laravel/framework/pull/48665
https://github.com/laravel/framework/pull/48673

For everyone else, let’s dive into it!

Overview of Breeze’s Authentication

A fresh install of Laravel with Breeze gives you basic authentication scaffolding, which includes registration, login, email verify, and password reset routes. It adds all of it’s files directly to your application, so you can easily modify them for your own needs.

Breeze itself supports a number of different stacks, depending on how you like to work. I’m just using the default Blade stack, but you can also get started in Livewire, Raw API, and Livewire with React or Vue. Each stack installs it’s own set of routes and controllers which all follow a similar pattern.

AuthenticatedSessionController

First up, let’s check out the `AuthenticatedSessionController` class, which handles authentication itself. Within this class is the `store()` method, which handles the actual login request from the user when they provide their username and password.

Let’s take a look at this method3:

use App\Http\Requests\Auth\LoginRequest;

class AuthenticatedSessionController extends Controller
{
    // ...

    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();

        $request->session()->regenerate();

        return redirect()->intended(RouteServiceProvider::HOME);
    }

    // ...
}

This action does two main things: