Categories
Laravel Laravel Basics Programming

What is Laravel Mass Assignment?

Learn what Laravel’s Mass Assignment is, how to do it and why you would use it.

Laravel is a PHP framework that is one if not the most popular in the world today. As of this writing Laravel 8 has been released, but the Laravel Mass Assignment concept is nothing new. Learn what it is, how to use mass assignments, its potential vulnerabilities and how to protect yourself from them.

What is Eloquent and ORM?

First some background information; Laravel provides an ORM (object-relational mapper) with its framework, known as Eloquent. It may sound intimidating but the idea of an ORM is this: To be able to write queries using the object-oriented paradigm of your preferred programming language, i.e. interacting with the database using your favorite programming language instead of SQL.

The ORM is the library that makes this possible. This is the library the implements this technique so to speak. In our case, it’s Eloquent.

With Eloquent, every database table has a corresponding Model used to interact with that table.

Broad overview of how Eloquent ORM works
This is a broad overview of using Eloquent

There are both pros and cons to using an ORM.

ORM Pros

  • You’ll get to write the queries in a programming language you already know.
  • You can easily switch between MySQL and any other SQL database.
  • If you’re not good at writing performant SQL then many of the queries you will write using an ORM will perform better than if you wrote them yourself.
  • Some ORM comes with features, both basic and advanced, straight out of the box, like migrations, seeds, connection pooling and much more.
  • The development can speed up due to these factors.

ORM Cons

  • You have to learn to use every ORM.
  • If you’re already good at writing performant queries they will probably perform better than if you used an ORM.
  • It can be a pain to setup an ORM if it doesn’t come plug and play with a framework.

So What is Mass Assignment?

So let’s get to mass assignment in Laravel. What is mass assignment? Are you ready?

Mass assignment is when you send an array of values to the model creation in a single go, instead of one-by-one.

That’s it. There’s no mystery behind this concept. It’s actually very simple.

// Imagine we have this form
<form method="POST" action="/signup">
   <input type="text" name="name">
   <input type="text" name="username">
   <input type="password" name="password">
   <input type="text" name="address">
   <input type="text" name="city">
   <input type="text" name="quote">
   // ... and 12 additional fields
   // ...
   // ...

   <button type="submit">Signup</button>
</form>
// In our controller - No mass assignment
public function store(Request $request) 
{     
   //perform validation     

   // Create a new user model
   $user = new User;

   // Without mass assignment, we would have to go
   // through each field manually
   $user->name = $request->get('name');     
   $user->user_name = $request->get('username');     
   $user->password = bcrypt($request->get('password'));     
   $user->address = $request->get('address');     
   $user->city = $request->get('city');
   $user->quote= $request->get('quote');  
   // ... plus the 12 additional fields

   // finally save    
   $user->save(); 
}

This obviously works, but it can be a bit annoying to outright tedious. It also adds a ton of code. There are horror stories on the internet about forms with hundreds of fields. Forms with 20ish fields are fairly common as well. Now let’s take a look at laravel mass assignments.

// In our controller - With mass assignment
public function store(Request $request) 
{     
   //perform validation     

   // Mass assignment
   $user = User::create($request->all());

   // return the inserted user
   return $user;
}

That’s it. All 18 fields immediately inserted. Very elegant. But as with everything, there are some cons.

Laravel Mass Assignment Vulnerability

Mass assignments come with vulnerabilities. This vulnerability is occurs when a user passes an unexpected HTTP request field and that field does something unintended in your database. Because remember, with Laravel mass assignments you just take the request and save it all at once. You can read more on Laravels own page.

So with our previous example, imagine the database looks something like this:

// inside your user migration
public function up()
{
     Schema::create('users', function (Blueprint $table) {
         $table->id();
         $table->string('username');
         $table->string('password');
         $table->unsignedTinyInteger('user_role')->default(2);
         
         // ...rest omitted for brevity
     });
}

This is a fairly common setup when there are user roles involved on a website. Maybe an even more common setup is to simply have a boolean is_admin column.

With this setup every registered user is given a user_role of “2” (which is a regular user). If an evil user passes an unexpected HTTP request field where user_role = 1 when registering a new user he would have created a new user with admin privileges. This can be done by just injecting their own HTML field with user_role value set to 1 for example.

This is a problem, so much that eloquent actually is protecting against using mass assignments by default. The developer has to actually point out each field that SHOULD be mass assignable.

Maybe you have gotten this error one too many times: Illuminate\Database\Eloquent\MassAssignmentException
Add [value] to fillable property to allow mass assignment on [App\Models\yourModel].

This is basically Laravel defending you from yourself and says “hey, if you want to mass assign you have to tell me which fields should be mass assignable”.

Laravel Properties Fillable and Guarded

To actually tell Laravel which fields should be mass assignable the framework supplies you with two properties you can add to your model, fillable and guarded.

Fillable

Fillable acts as a whitelist. It’s a keyword and takes an array with single values of the names of the fields you want to be able to mass assign.

class User extends Model
{
    use HasFactory;

    // fields that can be mass assignable
    protected $fillable = [
        'username',
        'password',
        // ...
    ];
}

That’s fillable. There’s really nothing else to it. It’s a whitelast that tells Laravel which fields can be mass assignable. By going off the previous examples, you would omit user_role here, since that would cause trouble. Instead you would handle that field manually.

Guarded

If fillable is Laravel’s whitelist then Guarded is Laravel’s blacklist. By adding Guarded you basically tell Laravel that all fields for that particular model are mass assignable, EXCEPT the ones listed in the guarded property.

class User extends Model
{
    use HasFactory;

    // fields that cannot be mass assignable
    protected $guarded= [
        'user_role',
        // ...
    ];
}

Fillable vs Guarded Properties

You only ever use one of the properties, never both on the same model. Using both doesn’t make sense since one acts as a blacklist and the other a whitelist.

So between fillable and guarded, which one should you choose? It doesn’t matter. They solve the same problem, mass assignment vulnerabilities, but do it opposite ways. It’s up to the developer’s preference.

Short Version, AKA TL;DR

  • Mass assignment is when you send an array of values to be saved all in on go, rather than go through each value one by one.
  • This saves up development time, looks more elegant and requires fewer lines of code.
  • This also opens up to mass assignment vulnerabilities which allows a user to pass unintended request fields which may be saved in your database along with the other fields if the user has knowledge of your database columns.
  • Because of this Laravel prevents mass assignments by default.
  • To allow mass assignments Laravel provides you with two properties, fillable and guarded. They act as a whitelist and blacklist respectively.
  • You only use one or the other in each model, never both in the same model.
// inside a controller method
// Mass assignment (make sure the model exists)
$user = User::create($request->all());

// return the inserted user
return $user;
// Inside your model
// fields that can be mass assignable
protected $fillable = [
     'username',
     'password',
     // ...
];

/* Use either fillable or guarded, never both
// fields that cannot be mass assignable
protected $guarded= [
     'is_admin',
];
*/