DEV Community

hbgl
hbgl

Posted on

Backend shorts - Validate all your inputs

Whenever you are dealing with untrusted data, such as input from a HTTP request, you must validate it thoroughly in the backend of your web application.

function store(Request $request) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'unit_price' => 'required|int|min:0|max:10000', 'currency' => 'required|in:USD,EUR', 'stock' => 'required|int|min:0|max: 1000000', ]); $product = new Product($validated); $product->save(); return redirect()->route('products.show', ['product' => $product->id]); } 
Enter fullscreen mode Exit fullscreen mode

One of the easiest and safest ways to read request data in Laravel is the $this->validate function inside a controller. It returns an array that contains the valid data (and only the valid data), or it throws a ValidationException if the data is invalid.

You should generally avoid reading data directly from the request object with $request->get('field') or similar methods, because it is very easy to forget to add the necessary validation. Reading data directly from the request object is a code smell.

Takeaways

  • When it comes to user input, be paranoid.
  • Always validate in the backend. Frontend validation offers no protection (although it is good for the UX).
  • Avoid reading unvalidated data directly from the request.

An elephant stretching for some leaves on a tree
Photo by Filip Olsok from Pexels

Bonus

Sometimes untrusted input data flies under the radar because it is not immediately accessible. A common example for this would be a CSV import where you first need to parse the CSV content in order to get to the fields. Nevertheless, it is vital to validate the parsed data.

public function importStore(Request $request) { // Validate the file metadata. $file = $this->validate($request, [ 'file' => 'required|file|mimes:txt,csv|max:5120', ])['file']; // Load CSV. $csv = Reader::createFromPath($file->path()); $csv->setHeaderOffset(0); $header = $csv->getHeader(); $input = iterator_to_array($csv->getRecords(), false); // Set a limit for the maximum number of products per import. if (count($input) > 1000) { throw ValidationException::withMessages([ 'file' => 'The import is limited to 1000 rows.', ]); } // Do a quick check to see if the header is correct. Although the // validation logic further below would also find the error, it // would produce multiple error messages for each line in the file. if ($header !== ['name', 'unit_price', 'currency', 'stock']) { throw ValidationException::withMessages([ 'file' => 'The CSV file does not have the right header.', ]); } // Validate CSV file content. $validated = Validator::make($input, [ '*' => 'required|array', '*.name' => 'required|string|max:255', '*.unit_price' => 'required|int|min:0|max:10000', '*.currency' => 'required|in:USD,EUR', '*.stock' => 'required|int|min:0|max: 1000000', ])->validate(); $instant = now(); foreach ($validated as &$entry) { $entry['created_at'] = $instant; $entry['updated_at'] = $instant; } Product::insert($validated); return redirect()->route('products.index'); } 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)