Support the ongoing development of Laravel.io →

Automatically Hash Laravel Model Values Using the "Hashed" Cast

23 Jan, 2024 6 min read 250 views

Photo by FlyD on Unsplash

Introduction

Hashing is an important security concept that every web developer should know about. It's what allows us to store passwords securely in our databases.

I won't be covering what hashing is in this article, so if you're not familiar with it, you might be interested in checking out my Guide to Encryption and Hashing in Laravel article.

In this short article, we're going to take a look at how to automatically hash model values in our Laravel projects before they're stored in the database.

Manually Hash Model Values

Typically, you may have been used to doing something like this in your Laravel code to manually hash a value:

use App\Models\User;
use Illuminate\Support\Facades\Hash;

$user = User::create([
    'name' => 'Ash',
    'email' => '[email protected]',
    'password' => Hash::make('password'),
]);

As a result of running the above code, the password field for the new user would be stored as something like so in the database:

$2y$10$Ugrfp6Myf9zVOo66KEuF9uQZ3hyg3T5GhJNgjOTy7o7AXCXSpwWpy

Automatically Hash Model Values

However, what if we wanted to automatically hash the password field for our User model without having to manually hash it each time?

To do this, we could use the hashed model cast that Laravel provides and was added in Laravel v10.10.0. This will automatically hash the value of the field before it's stored in the database.

Let's imagine we wanted to update our code example from above and remove the manual hashing of the password field. We'll first need to specify in our App\Models\User class that we want to use the hashed model cast for the password field. We can do this by updating the casts property on the model like so:

declare(strict_types=1);

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

final class User extends Authenticatable
{
    // ...

    protected $casts = [
        'password'=> 'hashed',
    ];
}

Now, when we create a new user, we can remove the manual hashing of the password field like so:

use App\Models\User;

$user = User::create([
    'name' => 'Ash',
    'email' => '[email protected]',
    'password' => 'password',
]);

It's worth noting that the hashed cast won't hash the value if it's already hashed. So if you're using Hash::make to manually hash the value as well as the hashed cast, you don't need to worry about the value being hashed twice.

Which Approach Should I Use?

A benefit of manually hashing the value is that it's more explicit and obvious that the value is being hashed. This means it can be easier for other developers to understand what's happening in the code at a glance. Whereas with the hashed model cast, it's not as obvious that the value is being hashed without the developer knowing about the model cast.

On the other hand, a benefit of using the hashed model cast is that it's less code to write. Although, I think this advantage is minimal though because you're not really removing that much code.

It's really important to remember that the casts are only applied when you're using the models to create or update records in the database. For example, if you're using something like the DB facade to create the user, their password won't be hashed using the hashed cast. So you'll need to remember to manually hash the value in this case.

In my opinion, I think both approaches are completely fine to use with their own pros and cons, and it's really down to personal preference. I'd just strongly recommend only using one approach in your project to avoid any confusion and be consistent. This will reduce the likelihood of someone making an assumption that a value is manually being hashed when it's not.

Testing That A Value Is Hashed

No matter which approach you choose, it's handy to have a test in place to ensure that the value is being hashed correctly.

This can be especially helpful in situations where you might accidentally assume that a value is being automatically hashed when it's not.

So let's take a quick look at a basic test that you could write to make sure that the value is being hashed when it's stored in the database.

We'll imagine that we have a basic App\Services\UserService class that we're using to create new users. The UserService class has a createUser method that accepts an imaginary NewUserData class. The NewUserData class has a password property that we want to ensure is hashed before it's stored in the database.

The UserService class might look something like so:

declare(strict_types=1);

namespace App\Services;

use App\DataTransferObjects\User;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

final readonly class UserService
{
    public function createUser(NewUserData $userData): User
    {
        return User::create([
            'name' => $userData->name,
            'email' => $userData->email,
            'password' => $userData->password,
        ]);
    }
}

We may want to write a test like so:

declare(strict_types=1);

namespace Tests\Feature\Services\UserService;

use App\DataObjects\NewUserData;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Hash;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

final class CreateUserTest extends TestCase
{
    use LazilyRefreshDatabase;

    #[Test]
    public function user_can_be_stored(): void
    {
        $newUserData = new NewUserData(
            name: 'Ash',
            email: '[email protected]',
            password: 'password',
        );

        $user = (new UserService())->createUser($newUserData);

        // Assert the user was stored in the database.
        $this->assertDatabaseHas(User::class, [
            'id' => $user->id,
            'name' => 'Ash',
            'email' => '[email protected]',
        ]);

        // Assert the password has been hashed correctly.
        $this->assertTrue(
            Hash::check('password', $user->password),
        );
    }
}

In the test above, we're calling the createUser method on the UserService class and passing in a NewUserData object with some dummy data. We're then asserting that the user was stored in the database.

Seeing as we can't directly compare the hashed value of the password with the value that we passed in using the assertDatabaseHas assertion, we can use the Hash::check method to check that the value is hashed correctly.

In the event that the value isn't hashed, the Hash::check method will return false and the test will fail. As a result, this means that if someone accidentally removes the hashing from the password field, we'll be able to spot it.

Conclusion

Hopefully, this short article has shown you how you can automatically hash your Laravel models fields.

If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.

You might also be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.

Or, you might want to check out my other 440+ ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.

Keep on building awesome stuff! 🚀

Last updated 3 weeks ago.

driesvints, ash-jc-allen, jerexbambex liked this article

3
Like this article? Let the author know and give them a clap!
ash-jc-allen (Ash Allen) I'm a freelance Laravel web developer from Preston, UK. I maintain the Ash Allen Design blog and get to work on loads of cool and exciting projects 🚀

Other articles you might like

November 18th 2024

Laravel Custom Query Builders Over Scopes

Hello 👋 Alright, let's talk about Query Scopes. They're awesome, they make queries much easier to r...

Read article
November 19th 2024

Access Laravel before and after running Pest tests

How to access the Laravel ecosystem by simulating the beforeAll and afterAll methods in a Pest test....

Read article
November 11th 2024

🍣 Sushi — Your Eloquent model driver for other data sources

In Laravel projects, we usually store data in databases, create tables, and run migrations. But not...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.