Automatically Hash Laravel Model Values Using the "Hashed" Cast
Tags: laravel, webdev, security, php
Published: 2024-01-23 09:51:26

## Introduction

Hashing is an [important security concept](https://ashallendesign.co.uk/blog/don-t-skimp-on-security-add-these-must-have-features-to-your-web-apps) 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](https://ashallendesign.co.uk/blog/a-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:

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

$user = User::create([
    'name' => 'Ash',
    'email' => 'mail@ashllendesign.co.uk',
    '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:

```text
$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:

```php
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:

```php
use App\Models\User;

$user = User::create([
    'name' => 'Ash',
    'email' => 'mail@ashallendesign.co.uk',
    '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:

```php
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:

```php
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: 'mail@ashallendesign.co.uk',
            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' => 'mail@ashallendesign.co.uk',
        ]);

        // 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](https://battle-ready-laravel.com)" which covers similar topics in more depth.

Or, you might want to check out my other 440+ ebook "[Consuming APIs in Laravel](https://consuming-apis-in-laravel.com)" which teaches you how to use Laravel to consume APIs from other services.

Keep on building awesome stuff! 🚀