Binding your own Laravel Auth Hash Library

Tag: Laravel v5.0+

Confession time - I wrote a "book". Yes, me too. Actually, I call it an "ePamphlet", and it is only about 8-10 
pages, completely FREE, and written very narrowly about the Laravel Collection class.

Laravel Collections Unraveled is just a deep dive into the class that I thought was too long for a blog post, 
and could be best presented via a LeanPub eBook. We'll dig into the source code of this popular class and see
some use cases for the major functions. 

Hey folks! A short one today based on a problem I ran into earlier this week that I thought would make a good tip. When Laravel first started getting popular, one of the "neat" things we all learned about was binding. We saw this most used in the repository method, binding our Eloquent repositories to their interfaces, but after a while a lot of folks started to realize that they were over-using it and that pattern wasn't, for smaller apps, as practical and necessary as they had originally thought.

That doesn't mean, however, that binding should be thrown out. I'd like to show you a simple binding trick with the Hasher Contract that will also serve as a good review of part of the authentication system.

Here's the scenario: You upgraded a Laravel app from 4.2 to 5.1, and are making the Auth library changes, when you realize that the last developers had, for reasons unknown, decided to hash the passwords with sha256. You have a database of existing users who will have to reset their passwords; the problem is discussed, and the decision is made that sha256 is fine for this case and you need to make the adjustments to the application.

There are a lot of different strategies for this. You might be tempted simply go into the AuthController and write new functions to handle the different hashing. The problem with this is the same one you get when you use Eloquent model functions in your controllers; eventually, you start duplicating login or password code. Over time you lose track of the different places, or a new developer joins the team and doesn't know about them. Things get out of sync.

We don't actually want to change all the processes, we only want to change the hash. So let's stick to that.

Here's how the current system works, as far as we are concerned. The default User creation happens in the `AuthController::create()`, and uses the php `bcrypt()` function, so we'll need to change that. To login, Laravel 5 has moved the code to a trait called `AuthenticatesUsers`. I found this out by looking at the top of the AuthController, which uses the `AuthenticatesAndRegistersUsers`, and tracing the code.

Moving along more quickly, we see that all of this is eventually calling `Guard` which is where our `Auth::login()`, `Auth::attempt()`, etc. actually reside. If you take a moment to trace some more, you will eventually get to the `EloquentUserProvider::validateCredentials()`. And there is where you will see the line:

return $this->hasher->check($plain, $user->getAuthPassword());    

So what is `$this->hasher`? You can see in the `__construct()` that a `HasherContract` is passed in. So where is this being bound? With a service provider, of course: `HashServiceProvider` Open it up, and ... Finally!

public function register()
{
    $this->app->singleton('hash', function () { return new BcryptHasher; });
}    

That was a long road, but we are better for it. We understand how things fit together, and where we need to make our changes. What we are actually aiming for in the end is that when we call `Hash::` we are working with sha256 rather than bcrypt. So, working backwards from above, we see that we would like to bind 'hash' to a new Sha256Hasher library - which we will make. We'll then overwrite the `HashServiceProvider` to bind to our own hasher before finally fixing that `bcrypt($input[password])` problem in the create().

So, let's get to it. I'm going to cut out a few bits for brevity's sake and let you match it to your own app style. To make things simple, let's create Sha256Hasher.php right in the main /app directory so we don't have to set up anything new in our composer.json. This needs to match the Hasher Contract (essentially, an Interface), so it will start like this:

namespace App;

use RuntimeException;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;

class Sha256Hasher implements HasherContract
{
    public function make($value, array $options = []) {}

    public function check($value, $hashedValue, array $options = []) {}

    public function needsRehash($hashedValue, array $options = []) {}
}    

If you look at the `BcryptHasher::make()` you'll see the guts of what we know as `Hash::make()`. The `check`, of course, is where we find out if the password they supply actually matches what we have in the database (you remember `return $this->hasher->check()` from above?) We're not going to get so fancy for this article; the following will suffice:

public function make($value, array $options = [])
{
    return hash('sha256', $value);
}

public function check($value, $hashedValue, array $options = [])
{
    if (strlen($hashedValue) === 0) {
        return false;
    }

    return (hash('sha256', $value) === $hashedValue);
}    

We have a couple of ways we can bind this. If you were doing something very small and simple, you could just use the `AppServiceProvider::register()` method and add the line:

$this->app->singleton('hash', function () { return new Sha256Hasher; });

If you do this, you must be sure to add that line to the `register()` method, not the boot; it seems that this service provider is called after the `AppServiceProvider` and so will bind it back to the original. (Also you could go into `/config/app.php` and comment out the line: 'Illuminate\Hashing\HashServiceProvider', then add to `AppServiceProvider::boot()`). If, on the other hand, you want to make your own `Sha256ServiceProvider`, just extend the `HashServiceProvider` and you will be fine. Remember to add your new service provider to the list in /config/app.php

To review to be sure you understand: now when you call `Hash::make('mypassword')`, it will call the function on `Sha256Hasher` instead. Likewise, it will use that class's `check()` for authenticating. So almost everything is encrypted as sha256.

Remember our `create()` method? We need to change that to use `Hash::make()` with our new logic. Instead of doing it here, though, let's add it as a `setAttribute` on the `User` class:

public function setPasswordAttribute($pass)
{
    $this->attributes['password'] = \Hash::make($pass);
}

There you go! Create a new user, login, and enjoy being sha256 encrypted! Hope that helped.

Read it Monday, Use it by Friday!

Laravel Quick Tips Weekly Newsletter. Short, immediately helpful bits you'll use in your own codebase before the next one arrives.

Join us now and get a FREE PDF of the first fix months of Laravel Quick Tips!

No Spam, Unsubscribe Anytime

Contact me