Search code examples
phplaravelvalidationlaravel-validation

Fixing Laravel form array validation recursion limits


Laravel Form Validation is great, except that it doesn't filter out extraneous keys when array notation is used.

I am type hinting the form request in my controller.

public function saveEdit(Post\EditRequest $request)
{
    $valid = $request->validated();
}

If my form has address_1 and address_2, and the user spoofs address_3, the spoofed value will not turn up in $valid.

However, if my form uses array notation, such as venue[address_1], and venue[address_2], then the user can spoof venue[address_3], and it will turn up in $valid.

Has anyone else come across this? How did you deal with it?

It seems like the validated() method on the form request class needs to operate recursively.


Solution

  • Unfortunately, Laravel doesn't include built-in support for validating array keys yet, only array values. To do so, we need to add custom validation rules.

    However, with alternative protection like $fillable model attributes and the array_only() helper function, creating these rules is a lot of extra work for a very unlikely edge case. We provide validation feedback for expected user error and sanitize to protect our data from unexpected input.

    If the user spoofs a key, as long as we don't save it, or we filter it out, they won't be too concerned if they don't see a pretty validation message—it doesn't make sense to validate a field that shouldn't exist in the first place.

    To illustrate how sanitization works, here's a Venue model that declares its fillable attributes:

    class Venue extends Model 
    {
        protected $fillable = [ 'address_1', 'address_2' ]; 
        ...
    }
    

    Now, if a malicious user attempts to spoof another attribute in the array:

    <input name="venue[address_1]" value="...">
    <input name="venue[address_2]" value="...">
    <input name="venue[address_3]" value="spoof!">
    

    ...and we use the input array directly to update a model:

    public function update(Request $request, $venueId)
    {
        $venue = Venue::find($venueId); 
        $venue->update($request->venue);
        ...
    }
    

    ...the model will strip out the additional address_3 element from the input array because we never declared it as a fillable field. In other words, the model sanitized the input.

    In some cases, we may need to simply sanitize array elements without using Eloquent models. We can use the array_only() function (or Arr:only()) to do this:

    $venueAddress = array_only($request->venue, [ 'address_1', 'address_2' ]);